【QT】元对象系统学习笔记(一)

QT元对象系统

      • 01、元对象系统
        • 1.1、 元对象运行原则
        • 1.2、 Q_OBJECT宏
        • 1.3、 Qt Creator启动元对象系统
        • 1.4、 命令行启动元对象(不常用)
      • 02、反射机制
        • 2.1、 Qt实现反射机制
        • 2.2、 反射机制获取类中成员函数的信息
          • 2.1.1、 QMetaMethon类
          • 2.1.2、QMetaObject类
        • 2.3、 反射机制获取类相关的信息

01、元对象系统

元对象系统是Qt对原有的C++进行的一些扩展,主要是为了实现信号和槽机制而引入的,信号和槽机制是Qt的核心特性。

Qt的元对象系统提供的功能有:
1、对象间通信的信号和槽机制
2、运行时类型信息和动态属性系统等

要使用元对象系统的功能,需要满足三个前置条件。

  1. 该类必须继承自QObject类。
  2. 必须在类申明的私有区域添加Q_OBJECT宏,该宏用于启动元对象特性,然后就可以使用动态特性、信号和槽等功能。
  3. 元对象编译器(moc)为每个QObject的子类提供实现了元对象特性所必须得代码。

1.1、 元对象运行原则

  • 因为元对象系统是对C++的扩展,因此使用传统的编译器是不能直接编译启用了元对象系统的Qt程序的,对此在编译Qt程序之前,需要把扩展的语法去掉,该功能就是moc要做的事情。
  • moc的全称是:Meta-Object Compiler(元对象编译器),它是一个工具(类似于qmake),该工具读取并分析C++源文件,若发现一个或多个包含了Q_OBJECT宏的类的申明,则会生成另外一个包含了Q_OBJECT宏实现代码的C++源文件(该源文件通常名称为:moc_*.cpp),这个新的源文件要么被#include包含到类 的源文件中,要么被编译键解到类的实现中(通常使用这种方法)。注意:新文件不会“替换”掉旧的文件,而是同源文件一起编译。

其他概念 :

1、元对象代码:指的是moc工具生成的源文件的代码,其中包含有Q_OBJECT宏的实现代码
2、moc工具的路径一般在安装路径下,如:C:\app\Qt5.8.0MinGw\5.8\mingw53_32\bin

1.2、 Q_OBJECT宏

先看一张图片,只要包含了Q_OBJECT宏,然后F2,就能进入。
在这里插入图片描述
可见,Q_OBJECT宏为申明的类之中增加了一些成员,而且红框标注的是虚函数成员(注意这些虚函数没有定义),按照C++语法,虚函数必须定义或者被申明为纯虚函数,moc工具的工作之一就是生成以上成员的定义,并且还会生成一些其他必要的代码。

1.3、 Qt Creator启动元对象系统

由于moc工具是通过Qt Creator来使用的,因此必须保证moc能发现并处理项目中包含有Q_OBJECT宏的类,所以,需要遵守以下规则:
1、从QObject派生的含有Q_OBJECT宏的类的定义必须在头文件中;
2、确保pro文件中,是否列举了项目中的所有源文件(SOURCES变量)和头文件(HEADERS变量);
3、应在头文件中使用逻辑指令如(#ifndef、#define、#endif)防止头文件重复包含多次。
4、QObject类应是基类列表中的第一个类。

由以上规则可见,使用 Qt Creator 编写代码时,类应定义在头文件中,成员函数
的定义应位于源文件中(这样可避免头文件被包含多次产生的重定义错误),虽然
这样编写程序比较麻烦,但这是一种良好的代码组织方式。

注意:如果定义了QObject类的派生类,并进行了构建(构建未添加O_OBJECT,而是在构建完成之后才添加),则此时必须再执行一次qmake命令,否则moc不能生成代码。

这里贴上书中的原话:
在这里插入图片描述
运行以上程序,可在 debug 目录下找到一个 moc_m.cpp 的源文件,该源文件就是使用 moc
工具生成的,该源文件中的代码就是元对象代码,读者可查看其代码。若在该目录没有
moc_m.cpp 文件,说明 moc 工具未能正常启动,这时需在 Qt Creator 中执行 qmake 命令,
再构建程序。

1.4、 命令行启动元对象(不常用)

  1. 在命令行需使用 moc 工具,并在源文件中包含 moc 工具生成的 cpp 文件。
  2. 此时包含 Q_OBJECT 的类不需要位于头文件中,假设位于 m.cpp 文件内,内容为:
#include<QObject>
class A : public QObject
{  Q_OBJECT
public:A(){}  
};int main(int argc, char *argv[]) {A ma; return 0; 
}
  1. 打开 Qt 5.8 for Desktop (MinGW 5.3.0 32 bit)命令行工具,输入如下命令:
moc d:\qt\m.cpp -o d:\qt\mm.cpp

以上命令会根据 m.cpp 生成 mm.cpp 文件,mm.cpp 文件中的代码就是元对象代码,此处
m.cpp 和 mm.cpp 都位于 d:\qt 文件夹下。

  1. 然后再次打开 m.cpp,在其中使用#include 把 mm.cpp 包含进去,如下:
#include <QObject>
//#include "mm.cpp"   // 不能把mm.cpp包含在类A的定义之前
class A : public QObject {Q_OBJECT
public:A(){}
};#include "mm.cpp"  // 必须把mm.cpp包含在类A的定义后面,因为mm.cpp源文件中有对A的成员定义,此时必须见到类A的完整定义。int main(int argc, char* argv[]) {A ma;return 0;
}
  1. 然后再使用 qmake 生成项目文件和 makefile 文件,再使用 mingw32-make 命令即可。

命令行这种方式,一般来说QT开发者是很少用到,因为Qt Creator已经帮我们做了这件事,这里只是看见书上有讲,记录一下。

02、反射机制

reflection模式(反射模式/反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。

元对象系统与反射机制:
1、元对象系统提供的功能之一是为 QObject 派生类对象提供运行时的类型信息及数据成
员的当前值等信息,也就是说,在运行阶段,程序可以获取 QObject 派生类对象所属
类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是
反射机制。

2、因为 Qt 的元对象系统必须从 QObject 继承,又从反射机制的主要作用可看到,Qt 的
元对象系统主要是为程序提供了 QObject 类对象及其派生类对象的信息,也就是说不
是从 QObject 派生的类对象,则无法使用 Qt 的元对象系统来获取这些信息。

元对象的具体解释:是指用于描述另一个对象结构的对象。

具象化到程序上如下:

class B
{//TODO....
};class A : public B
{B mb;
};

假设mb是用来描述类A创建的对象的,则mb就是元对象。

2.1、 Qt实现反射机制

  1. Qt使用了一系列的类来实现反射机制,这些类对对象的各个反面进行了描述,其中QMetaObject类描述了QObject及其派生类对象的所有元信息,该类是Qt元对象系统的核心类,通过该类的成员函数可以获取QObject及其派生类对象的所有元信息,因此可以说QMetaObject类的对象是Qt中的元对象。 注意:要调用QMetaObject类中的成员函数需要使用QMetaObject类型的对象。
  2. 对对象的成员进行描述:一个对象包含数据成员、函数成员、构造函数、枚举成员等,在Qt中,这些成员分别使用了不同的类对其进行描述。比如函数成员使用类QMetaMethod、属性使用QMetaProperty进行描述等。最后使用QMetaObject类对整个类对象进行描述,比如要获取成员函数的变量名:
QMetaMethod qm = metaObject->method(1);  // 获取索引为1的成员函数
qDebug()<< qm.name() << "\n";  // 输出该成员函数的名称

使用Qt反射机制的前置条件:

  1. 需要继承自QObject类,并需要在类之中加入Q_OBJECT宏。
  2. 注册成员函数:若希望普通成员函数能够被反射,需要在函数声明之前加入QObject::Q_INVOKABLE宏。
  3. 注册成员变量:若希望成员变量能够被反射,需要使用Q_PROPERTY宏。

其实关于QObject::Q_INVOKABLEQ_PROPERTY这两个宏,在Qt5中的QQuick中就有很深切的体会,QML要使用C++这边的属性、方法都是需要注明这个宏的。

Qt 反射机制实现原理简述:

  1. Q_OBJECT宏展开之后有一个虚拟成员函数meteObject(),该函数会返回一个指向QMetaObject类型的指针,其原型为:
virtual const QMetaObject* metaObject() const;

因为启动了元对象系统的类都包含了Q_OBJECT宏,所以这些类都含有metaObject()虚拟成员函数,通过该函数返回的指针调用QMetaObject类中的成员函数,便可以查询到QObject及其派生类对象的各种信息。

  1. Qt的moc会完成以下工作:
    1、为Q_OBJECT宏展开后所声明的成员函数的生成实现代码;
    2、识别Qt中特殊的关键字及宏,比如识别出Q_PROPRTYQ_INVOKABLEslotsignals等。

2.2、 反射机制获取类中成员函数的信息

2.1.1、 QMetaMethon类

作用:用于描述对象的成员函数,可使用该类的成员函数获取对象成员函数的信息。

列举一些常用的成员:

// 此枚举用于描述函数的类型,即:普通成员函数、信号、槽、构造函数
enum MethodType {Method, Signal, Slot, Constructor}// 此枚举主要用于描述函数的访问级别(私有的、受保护的、公有的)
enum Access {Private,Protected,Public}// 返回函数的签名(qt5.0)
QByteArray methodSignature() const;// 返回函数的类型(信号、槽、成员函数、构造函数)
MethodType methodType() const;// 返回函数的名称(qt5.0)
QByteArray name() const;// 返回函数的参数数量(qt5.0)
int parameterCount() const;// 返回函数参数名称的列表
QList<QByteArray> parameterNames() const;// 返回指定索引处的参数类型,返回值是使用QMetaType注册的类型,若类型未注册,则返回值为QMetaType::UnknownType。
int parameterType(int index) const;// 返回函数参数类型的列表
QList<QByteArray> parameterTypes() const;// 返回函数的返回类型。返回值是使用QMetaType注册的类型,若类型未注册,则返回值为QMetaType::UnknownType。
int returnType() const;// 返回函数的返回类型的名称
const char* typeName() const;// 返回函数的访问级别(私有的、受保护的、公有的)
Access access() const;
2.1.2、QMetaObject类

作用:用于提供关于类的元对象信息。

列举一些常用的成员:

/* 返回名为 f 的函数的索引号,否则返回-1。此处应输入正确的函数签名,比如函数形
式为 void f(int i,int j);则正确的形式为 xx.indexOfMethod("f(int,int"); 以下形式都不是
正确的形式,"f(int i, int j)"、"void f(int, int)"、 "f"、"void f"等。*/
int indexOfMethod(const char* f) const;// 返回信号 s 的索引号,否则返回-1,若指定的函数存在,但不是信号,仍返回-1。
int indexOfSignal(const char * s) const;// 返回构造函数 c 的索引号,否则返回-1
int indexOfConstructor(const char *c) const;// 返回构造函数的数量。
int constructorCount() const ; // 返回指定索引 i 处的构造函数的元数据。
QMetaMethod constructor(int i)const;// 返回函数的数量,包括基类中的函数、信号、槽和普通成员函数。
int methodCount() const;// 返回父类中的所有函数的总和,也就是说返回的值是该类中的第一个成员函数的索引位置。
int methodOffset() const;// 返回指定索引 i 处的函数的元数据。
QMetaMethod method(int i) const;

下面是书上的一个示例,仅供参考

#include "m.h"
#include <QMetaMethod>
#include <QByteArray>
#include <iostream>
using namespace std;
int main(){  A ma; B mb; //创建两个对象
const QMetaObject *pa=ma. metaObject ();
const QMetaObject *pb=mb. metaObject ();
//以下为通过 QMetaObject 的成员函数获取的信息。
int j=pa->methodCount(); /*返回对象 ma 中的成员函数数量,包括从父类 QObject 继承而来的 5 个
成员函数及本对象中的 2 个成员函数(注意,不包括 g1)、1 个信号,所以
总数为 8。*/
cout<<j<<endl; //输出 8
int i=pa->indexOfMethod("g(int,float)"); //获取对象 ma 中的成员函数 g 的索引号。
cout<<i<<endl; //输出 7
i=pa->constructorCount(); //获取对象 ma 所属类中的构造函数的数量。
cout<<i<<endl; //输出 2
i=pb->constructorCount(); /*获取对象 mb 所属类 B 中的构造函数的数量,因类 B 无构造函数,所以
返回值为 0,此处也可看到,构造函数数量不包含父类的构造函数*/
cout<<i<<endl; //输出 0。
i=pa->indexOfConstructor("A(int)"); //获取对象 ma 所属类中的构造函数 A(int)的索引号。
cout<<i<<endl; //输出 1。
i=pa->indexOfSignal("gb3()"); //获取对象 ma 的信号 gb3()的索引号。
cout<<i<<endl; //输出 5。
i=pa->indexOfSignal("f()"); /*获取对象 ma 的信号 f()的索引号。因为成员函数 f 存在,但不是信
号,所以返回值为-1。*/
cout<<i<<endl; //输出-1。
i=pb->methodOffset(); /*获取父类的成员函数数量,包括父类A及QObject中的成员函数,总共为8。
*/
cout<<i<<endl; //输出 8,此处相当于是对象 mb 自身成员函数开始处的索引号。
//以下为通过 QMetaMethon 的成员函数获取的信息。
//获取对象 ma 的成员函数 g 的元数据。
QMetaMethod m=pa->method(pa->indexOfMethod("g(int,float)"));
QByteArray s= m.name(); //获取成员函数 g 的函数名。
cout<<s.data()<<endl; //输出 g
s=m.methodSignature(); //获取函数 g 的签名
cout<<s.data()<<endl; //输出 g(int,float)
i=m.methodType(); /*获取函数 g 的类型,此处返回的是 QMetaMethod::MethodType 中定义的枚举值,
其中 Method=0,表示类型为成员函数*/
cout<<i<<endl; //输出 0(表示成员函数)。
//以下信息与函数的返回类型有关
s=m.typeName(); //获取函数 g 的返回值的类型名
cout<<s.data()<<endl; //输出 void
i=m.returnType(); /*获取函数 g 返回值的类型,此处的类型是 QMetaType 中定义的枚举值,其中枚举
值 QMetaType::void=43*/
cout<<i<<endl; //输出 43
//以下信息与函数的参数有关
i=m.parameterType(1); /*获取函数 g 中索引号为 1 的参数类型,此处的类型是 QMetaType 中定义的
枚举值,其中枚举值 QMetaType::float=38*/
cout<<i<<endl; //输出 38
QList<QByteArray> q=m.parameterNames(); //获取函数 g 的参数名列表
cout<<q[0].data()<<q[1].data()<<endl; //输出 ij
q=m.parameterTypes(); //获取函数 g 的参数类型列表。
cout<<q[0].data()<<q[1].data()<<endl; //输出 intfloat
return 0; }

2.3、 反射机制获取类相关的信息

  1. QMetaObject类中获取与类相关的信息的成员函数有:
/*获取类的名称,注意,若某个 QObject 的子类未启动元对象系统(即未使用 Q_OBJECT
宏),则该函数将获取与该类最接近的启动了元对象系统的父类的名称,而不再返回
该类的名称,因此建议所有的 QObject 子类都使用 Q_OBJECT 宏。
*/
const char* className() const;// 返回父类的元对象,若没有这样的对象则返回0
const QMetaObject* superClass() const;// 若该类继承自mo描述的类型,则返回true,否则返回false。类被认为继承自身。
bool inherits(const QMetaObject* mo) const;  // (Qt5.7)
  1. QObject类中获取与类相关的信息的成员函数有:
// 若该类是className指定的类的子类则返回true,否则返回false。类被认为继承自身
bool inherits(const char* className) const;

下面是一个示例:

头文件:

#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include<QObject>
class A:public QObject{ Q_OBJECT};
class B:public A{ Q_OBJECT};
class C:public QObject{Q_OBJECT};
class D:public C{};
#endif // M_H

源文件:

#include "m.h"
#include <QMetaMethod>
#include <iostream>
using namespace std;
int main(){  A ma; B mb; C mc; D md;
const QMetaObject *pa=ma. metaObject ();
const QMetaObject *pb=mb. metaObject ();
cout<<pa->className()<<endl; //输出类名 A
//使用 QMetaObject::inherits()函数判断继承关系。
cout<<pa->inherits(pa)<<endl; //输出 1,类被认为是自身的子类
cout<<pa->inherits(pb)<<endl; //输出 0,由 pb 所描述的类 B 不是类 A 的子类。
cout<<pb->inherits(pa)<<endl; //输出 1,由 pa 所描述的类 A 是类 B 的子类。
//使用 QObject::inherits()函数判断继承关系。
cout<<ma.inherits("B")<<endl; //输出 0,类 A 不是类 B 的子类。
cout<<ma.inherits("A")<<endl; //输出 1,类被认为是自身的子类
cout<<md.inherits("D")<<endl; //输出 0,因为类 D 未启动元对象系统。
cout<<md.inherits("C")<<endl; /*输出 1,虽然类 D 未启动元对象系统,但类 C 已启动,此种情形下
能正确判断继承关系。*/
cout<<md. metaObject ()->className()<<endl; /*输出 C,此处未输出 D,因为类 D 未启动元对象系统,
与类 D 最接近的启动了元对象系统的父类是 C,因此返回 C。*/
return 0; }
  1. qobject_cast函数,使用语法如下:
DestType* qobject_cast<DestType*>(QObject* p);

1、该函数类似于 C++中的 dynamic_cast,但执行速度比 dynamic_cast 更快,且不需要
C++的 RTTI 的支持,但 qobject_cast 仅适用于 QObject 及其派生类。
2、主要作用是把源类型 QObject 转换为尖括号中的目标类型 DesType(或者子类型),并
返回指向目标类型的指针,若转换失败,则返回 0。或者说源类型 QObject 属于目标
类型 DestType(或其子类型),则返回指向目标类型的指针,否则返回 0。
3、使用 qobject_cast 的条件:目标类型 DestType 必须继承(直接或间接)自 QObject,并
使用 Q_OBJECT 宏。

一个示例:

头文件:

#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include<QObject>
#include <iostream>
using namespace std;
class A:public QObject{ Q_OBJECT
public:void fa(){cout<<"FA"<<endl;}  };
class B:public A{ Q_OBJECT
public:void fb(){cout<<"FB"<<endl;}  };
class C:public QObject{Q_OBJECT
public:void fc(){cout<<"FC"<<endl;}  };
class D:public C{ public: void fd(){cout<<"FD"<<endl;} };
#endif // M_H

源文件:

#include "m.h"
#include <QMetaMethod>
#include <iostream>
using namespace std;
//qobject_cast 的简单应用(类型判断)
void g(QObject *p){
if(qobject_cast<A*>(p)) //若 p 是类 A 及其派生类类型
{cout<<"GA"<<endl;}
if(qobject_cast<B*>(p))//若 p 是类 B 及其派生类类型
{cout<<"GB"<<endl;}
else //若 p 不是类 B 及其派生类类型
cout<<"XX"<<endl;  }
int main(){  A *pa=new A; B *pb=new B; C *pc=new C; D *pd=new D;
qobject_cast<B*>(pa)->fb(); //输出 FB,转换成功后可调用子类中的函数。
//qobject_cast<D*>(pc); //错误,因为类 D 未使用 Q_OBJECT 宏。
g(pa); //输出 GA、XX。因为 pa 不是 B 及其派生类类型所以会输出 XX。
g(pb); //输出 GA、GB。因为 pb 是 A 的派生类类型,所以首先输出 GA,然后输出 GB。
g(pc); //输出 XX,因为 pc 即不是 A 也不是 B 及其派生类的类型,所以输出 XX。
g(pd); //输出 XX,原因同上。
return 0; }

未完待续!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/27965.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何快速又高质量的输出PDF实验报告?

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 PDF文档的打印在很多应用场景中都会被使用到&#xff0c;最为常见的就是实验室信息管理…

xred病毒分析

概述 xred病毒是一种感染型病毒&#xff0c;会感染系统中特定目录下的exe和xlsx文件&#xff0c;该病毒会将自身伪装成Synaptics触摸板驱动程序&#xff0c;使用Dephi编写。 样本的基本信息 Verified: Unsigned Link date: 6:22 1992/6/20 Company: Synaptics Description: S…

华南农业大学|图像处理与分析技术综合设计|题目解答:定位数显区域并分离电表数字

设计任务 图 28 是一幅正在运行的数字电表图像&#xff08;ipa28.jpg&#xff09;&#xff0c;试采用图像处理与分析 技术&#xff0c;设计适当的算法和程序&#xff0c;找出电表的数显区域&#xff0c;计算目标区域的长宽比 和像素面积&#xff1b;并提取其中面积最大的 …

Service:微服务架构的应对之道

Service 的工作原理和 LVS、Nginx 差不多&#xff0c;Kubernetes 会给它分配一个静态 IP 地址&#xff0c;然后它再去自动管理、维护后面动态变化的 Pod 集合&#xff0c;当客户端访问 Service&#xff0c;它就根据某种策略&#xff0c;把流量转发给后面的某个 Pod。 Service 使…

Docker把公共镜像推送到harbor私服的流程(企业级)

如果构建项目时&#xff0c;使用了k8s docker Jenkins的模式。 那么我们在docker构建镜像时&#xff0c;如果需要使用了Nodejs&#xff0c;那么我们必须得从某个资源库中拉取需要的Nodejs。 在企业里&#xff0c;正常都会把自己项目涉及的库都放在harbor私服里。 下面讲一下&…

珠海电子行业实行MES系统该如何规划 mes系统供应商 先达盈致

在电子行业中&#xff0c;MES系统是提高生产效率、降低成本、优化生产计划的关键工具。但是&#xff0c;如何规划MES系统的实施才能使其大限度地提高企业的生产效率和经济效益呢&#xff1f; 首先&#xff0c;企业应该充分了解MES系统的基本概念和功能模块。MES系统中包括生产…

PowerDesigner 数据库建模使用详解

目录 一、前言 二、PowerDesigner概述 2.1 PowerDesigner核心能力 2.1.1 集成多种建模能力 2.1.2 自动生产代码能力 2.1.3 强大的逆向工程能力 2.1.4 可扩展的企业库解决方案 2.2 PowerDesigner常用的几种模型 2.2.1 概念模型 2.2.2 逻辑数据模型 2.2.3 物理模型 2.2…

华为云CodeArts Check IDE插件体验之旅

1 开发者的思考 近年来&#xff0c;ChatGPT的来临像一场突然出现的风暴&#xff0c;程序员是否马上被取代的担忧出现在媒体上了&#xff0c;作为软件开发小白&#xff0c;前不久我也陷入了这样的深思之中&#xff0c;但认真的想了下&#xff0c;ChatGPT就如自动驾驶一样&#…

详解Windows安装分布式版本控制系统git

文章目录 前言下载安装相关链接 前言 git是一个分布式版本控制软件&#xff0c;最初由Linux创作者Linus Torvalds创作&#xff0c;并于2015年以GPL许可协议发布。git易于学习&#xff0c;占用空间小&#xff0c;性能却快如闪电&#xff0c;可以快速、 高效的管理从小到大的项目…

IP扫描工具

什么是高级 IP 扫描仪 高级 IP 扫描程序是网络中必不可少的工具&#xff0c;使管理员能够跟踪网络地址空间。在提供要扫描的 IP 地址范围时&#xff0c;高级 IP 扫描程序会借助网络扫描协议按顺序检查该范围内的每个 IP&#xff0c;扫描后&#xff0c;高级 IP 扫描程序工具可查…

Python 使用 Stable Diffusion API 生成图片示例

代码&#xff1a; import base64 import datetime import json import osimport requestsdef submit_post(url: str, data: dict):"""Submit a POST request to the given URL with the given data.:param url: url:param data: data:return: response"…

Unity 之 安卓平台上架隐私问题解决方案

Unity 之 助力游戏增长 -- 解决隐私问题 一&#xff0c;平台测试隐私问题二&#xff0c;解决方式一2.1 勾选自定义Mainifest2.2 修改自定义Mainifest2.3 隐私协议弹窗逻辑 三&#xff0c;解决方式二3.1 导出安卓工程3.2 创建上层Activity3.3 配置AndroidManifest 四&#xff0…