来源 https://zhuanlan.zhihu.com/p/36798160
Qt5支持编写AndroID应用。
典型main
:
int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); return a.exec();}
这会在AndroID设备上显示一个空白窗口。
但是:
问题1,main
函数“冲突”。我们知道AndroID进程源于zygote的fork,作为进程入口的函数main
早就执行过了,那么上述代码这中Qt的入口函数main
又何时怎么被执行的呢?问题2,AndroID activity的生命周期是如何转化为qt的生命周期的?问题3,qt主线程和AndroID主线程的关系是什么样的?以下基于Qt 5.10.1分析(源码参考https://github.com/qt/qtbase/releases/tag/v5.10.1)。
helloworld工程分析在qtcreator新建一个helloworld的工程,编译后,在qt的build目录下,可以看到目录结构:
- androID_build/- libhelloworld.so- main.o- mainwindow.o
.o文件显然对应各个cpp文件,so文件是.o文件的“集合”。androID-build的目录经过简单的查看,可以知道是一个gradle组织的androID工程。
所以qt的AndroID支持,简单看就是将我们写的qt代码生成so文件,并通过自动生成的AndroID模板工程来最终生成一个apk文件。
看下androID_build中的build.gradle:
……sourceSets { main { manifest.srcfile 'AndroIDManifest.xml' java.srcDirs = [qt5AndroIDDir + '/src', 'src', 'java'] aIDl.srcDirs = [qt5AndroIDDir + '/src', 'src', 'aIDl'] res.srcDirs = [qt5AndroIDDir + '/res', 'res'] resources.srcDirs = ['src'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jnilibs.srcDirs = ['libs'] }}……
build.gradle中通过sourceSets
调整了com.androID.application
插件的默认源码、资源路径。
主要引入了qt5AndroIDDir
目录下的src
、aIDl
和res
。
qt5AndroIDDir
定义在gradle.propertIEs
:
qt5AndroIDDir=/Users/xxxx/Qt5.10.1/5.10.1/androID_armv7/src/androID/java
一般指向qt安装目录中的androID_armv7/src/androID/java
.
看下libs目录:
libs├── QtAndroID-bundled.jar└── armeabi-v7a ├── gdbserver ├── libQt5Core.so ├── libQt5Gui.so ├── libQt5Widgets.so ├── libgdbserver.so ├── libgnustl_shared.so ├── libhelloworld.so ├── libplugins_imageformats_libqgif.so ├── libplugins_imageformats_libqicns.so ├── libplugins_imageformats_libqico.so ├── libplugins_imageformats_libqjpeg.so ├── libplugins_imageformats_libqtga.so ├── libplugins_imageformats_libqtiff.so ├── libplugins_imageformats_libqwbmp.so ├── libplugins_imageformats_libqwebp.so ├── libplugins_platforms_androID_libqtforandroID.so └── libplugins_styles_libqandroIDstyle.so
qt运行所需的几个核心so拷贝被拷贝到了libs目录下,这样就会被打包到最终的apk中。还引入了一个QtAndroID-bundled.jar
依赖。
总结
qt编写的代码会生成为一个动态库,以工程名命名qt生成AndroID应用的策略是借助了一个模板工程不难猜测,qt应用的运行模式是,通过模板工程生成的AndroID应用,以native调用方式执行qt代码启动流程
上节分析可知,AndroID代码主导了整个进程的运行。那么寻找Qt应用入口就从AndroID代码入手。
AndroID应用入口一般是Application
,模板工程androID-build
中,QtApplication
继承了Applicaiton
,但是浏览一遍并没有发现加载libhelloworld.so
的地方,先略过。
Application
加载后会会执行“主Activity”,也就是<intent-filter>
指定有category.LAUNCHER
的那个Activity
,在模板工程中是QtActivity
。
这样,主Activity的onCreate
就不亚于第二入口:
@OverrIDepublic voID onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); onCreateHook(savedInstanceState);}
onCreateHook
protected voID onCreateHook(Bundle savedInstanceState) { m_loader.APPliCATION_ParaMETERS = APPliCATION_ParaMETERS; m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES; m_loader.QT_ANDROID_themeS = QT_ANDROID_themeS; m_loader.QT_ANDROID_DEFAulT_theme = QT_ANDROID_DEFAulT_theme; m_loader.onCreate(savedInstanceState);}
这里看到的m_loader
是类QtActivityLoader
Loader
QtActivityLoader.create
:
……m_displayDensity = m_activity.getResources().getdisplayMetrics().densityDpi;ENVIRONMENT_VARIABLES += "\tQT_ANDROID_theme=" + QT_ANDROID_DEFAulT_theme + "/\tQT_ANDROID_theme_disPLAY_DPI=" + m_displayDensity + "\t";if (null == m_activity.getLastNonConfigurationInstance()) {//代码分析看应该总是null,可能是预留的功能吧 if (m_contextInfo.MetaData.containsKey("androID.app.background_running") && m_contextInfo.MetaData.getBoolean("androID.app.background_running")) { ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t"; } else { ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t"; } if (m_contextInfo.MetaData.containsKey("androID.app.auto_screen_scale_factor") && m_contextInfo.MetaData.getBoolean("androID.app.auto_screen_scale_factor")) { ENVIRONMENT_VARIABLES += "QT_auto_SCREEN_SCALE_FACTOR=1\t"; } startApp(true);//上面大部分只是在设置ENVIRONMENT_VARIABLES,这里是关键}
找到一个亲切的函数——startApp
:
//查AndroIDManifest.xml,易得, <Meta-data androID:name="androID.app.use_local_qt_libs" androID:value="1"/>if (m_contextInfo.MetaData.containsKey("androID.app.use_local_qt_libs") && m_contextInfo.MetaData.getInt("androID.app.use_local_qt_libs") == 1) { ……//根据AndroIDManifest和ENVIRONMENT_VARIABLES的值来配置loaderParams,此处省略一万字 //这里调用loaderClassname()设置LOADER_CLASS_name,对于QtActivityLoader是org.qtproject.qt5.androID.QtActivityDelegate(loaderClassname()函数的名字取得不准确,个人更趋向于叫delegaterClassname()) loaderParams.putString(LOADER_CLASS_name_KEY, loaderClassname()); loadApplication(loaderParams); return;}//如果不使用本地qt库,则绑定ministro的服务,并且在绑定后会启动下载流程if (!m_context.bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalname()), m_ministroConnection, Context.BIND_auto_CREATE)) { throw new SecurityException("");}
startApp
处理了是否需要ministro
介入Qt启动流程的事,ministro
可以不在应用中嵌入qt库,而是在运行的时候去下载必要库。QtCreator
创建的工程生成的apk是自带qt库的,读者可以忽略ministro
.
最后startApp
调用了loadApplication
——加载Qt库并运行:
……//这里上文分析了,加载到的是类是:org.qtproject.qt5.androID.QtActivityDelegateClass<?> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_name_KEY)); // load QtLoader classObject qtLoader = loaderClass.newInstance(); // create an instance//反射调用loadApplicationMethod prepareAppMethod = qtLoader.getClass().getmethod("loadApplication", contextClassname(), ClassLoader.class, Bundle.class);if (!(Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams)) throw new Exception("");QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);//这里会加载libhelloworld.so,以及它依赖的其他库,如libQt5Core.so等if (libname != null) System.loadlibrary(libname);//反射调用startApplicationMethod startAppMethod=qtLoader.getClass().getmethod("startApplication");if (!(Boolean)startAppMethod.invoke(qtLoader)) throw new Exception("");
这里涉及qt AndroID封装中一个重要的类——delegate。对于QtActivity
而言是QtActivityDelegate
.
到此为止,接触了QtActivity, QtActivityLoader , QtActivityDeleagate,这3个类是其AndroID封装中很重要的类,注意梳理3个类间关系。
startApp
主要工作是找到loaderClass(成为delegate更合适),然后调用它的loadApplication
和startApplication
.
Delegate
loadApplication
主要用来读取loadParams
,并设置一些全局值,不是本文重点,不深入分析。
startApplication
是重头戏:
//qtbase-5.10.1/src/androID/jar/src/org/qtproject/qt5/androID/QtActivityDelegate.javapublic boolean startApplication() { …… if (null == m_surfaces) onCreate(null); ……}public voID onCreate(Bundle savedInstanceState) { …… //创建一个名字是startApplication的“回调” startApplication = new Runnable() { @OverrIDe public voID run() { try { String nativelibraryDir = QtNativelibrarIEsDir.nativelibrarIEsDir(m_activity); //调用QtNative.startApplication QtNative.startApplication(m_applicationParameters, m_environmentvariables, m_mainlib, nativelibraryDir); m_started = true; } catch (Exception e) { e.printstacktrace(); m_activity.finish(); } } }; //创建一个布局,startApplication回调将在QtLayout.onSizeChanged重载中调用 m_layout = new QtLayout(m_activity, startApplication);//QtLayout extends VIEwGroup …… //设置为ContentVIEw m_activity.setContentVIEw(m_layout, new VIEwGroup.LayoutParams(VIEwGroup.LayoutParams.MATCH_PARENT, VIEwGroup.LayoutParams.MATCH_PARENT)); ……}
QtActivityDelegate
的startApplication
调用了自己的onCreate
,onCreate
中主要是创建一个QtLayout
,作为contentVIEw
,并在QtLayout onSizeChanged
的时候调用局部变量startApplication
指向的回调。然后启动任务抛给了QtNative.startApplication
.
QtNative.startApplication
:
public static boolean startApplication(String params, String environment, String mainlibrary, String nativelibraryDir) throws Exception{ synchronized (m_mainActivityMutex) { res = startQtAndroidplugin(); …… startQtApplication(f.get@R_502_4613@Path() + params, environment);//native startQtApplication m_started = true; }}public static native boolean startQtAndroidplugin();public static native voID startQtApplication(String params, String env);
调到了native方法,实现在qtbase-5.10.1/src/plugins/platforms/androID/androIDjnimain.cpp:
static jboolean startQtAndroidplugin(jnienv* /*env*/, jobject /*object*//*, jobject applicationAssetManager*/){ m_androidplatformIntegration = nullptr; m_androIDAssetsfileEngineHandler = new AndroIDAssetsfileEngineHandler(); return true;}static jboolean startQtApplication(jnienv *env, jobject /*object*/, Jstring paramsstring, Jstring environmentString){ //通过dlopen+dlsym定位libhelloworld.so中的main函数,也就是qt代码的main函数 m_mainlibraryHnd = dlopen(m_applicationParams.constFirst().data(), 0); if (Q_UNliKELY(!m_mainlibraryHnd)) { qCritical() << "dlopen Failed:" << dlerror(); return false; } m_main = (Main)dlsym(m_mainlibraryHnd, "main"); …… //创建线程调用startMainMethod jboolean res = pthread_create(&m_qtAppThread, nullptr, startMainMethod, nullptr) == 0; ……}static voID *startMainMethod(voID */*data*/){ …… int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data())); ……}
这里native代码中的startQtApplication
通过dlsym
定位了qt代码中的main
函数,然后创建了一个线程来执行这个main
函数的代码(所以qt主线程,并不是AndroID的主线程)。而main
函数,我们知道会一直执行QApplication.exec()
直到退出。
至此,qt工程生成的libhelloworld.so开始执行main函数,开始表现得就像传统的桌面qt应用一样。
让我们梳理下。
总结
主要的启动流程是QtActivity.create -> QtActivityLoader.startApp -> QtActivityDelegate.startApplication -> QtNative.startApplicaitonqt的主线程并不是AndroID的主线程,二者相互独立qt的main
函数并不是androID应用中的入口函数,只是用作androID代码启动qt代码的调用入口(其实c语言中的main也有异曲同工之处)AndroIDManifest中留了很多“启动”qt代码的参数退出流程
入口找到了,找下出口。
传统qt应用,会在主窗口关闭后,事件循环结束,然后QApplication.exec()
退出,主函数退出。
AndroID中qt创建的“窗口”实际上是Activity中的一块Surface。而且我们知道AndroID Activity的生命周期onDestroy算是退出。但,也不尽然,因为onDestroy后,进程可以驻留后台等待系统唤起。
那么,qt的AndroID封装是如何对接的?
先看其QtActivity的lauchMode:androID:launchMode="singletop"
singletop,简而言之:如果已经在栈顶,复用,否则,重新创建。这意味着,一旦QtActivity退到其他Activity后面,下次回到栈顶就需要重新创建。
所以onDestroy
是一个很合理的“退出点”。这和之前的onCreate
作为启动入口正好在AndroID生命周期上是对应的。
onDestroy
之前onStop
会先触发。
@OverrIDeprotected voID onStop(){ super.onStop(); QtApplication.invokeDelegate();//??}@OverrIDeprotected voID onDestroy(){ super.onDestroy(); QtApplication.invokeDelegate();//??}
onStop和onDestropy都是一句QtApplication.invokeDelegate
搞定,挺优雅的实现,不过对于分析流程而言有点障碍,我们先看下究竟invokeDelegate
做了什么?
分析别人代码,笔者喜欢猜流程。
比如这里,之前分析启动流程的时候QtActivity是一个壳,调用QtActivityLoader帮忙加载libhelloworld.so,而真正的又处理发生在QtActivityDelegate.这和QtApplication.invokeDelegate应该不会只是名字上的巧合。
可以合理猜测QtApplication.invokeDelegate的任务是“优雅”地把QtActivity要做的事委托给QtActivityDelegate。
带着这个猜测分析看看吧。
QtApplication.invokeDelegate
private static int stackDeep=-1;public static InvokeResult invokeDelegate(Object... args){ InvokeResult result = new InvokeResult(); if (m_delegateObject == null) return result; //通过调用栈查找要被代理的函数 StackTraceElement[] elements = Thread.currentThread().getStackTrace(); if (-1 == stackDeep) { for (int it=0;it<elements.length;it++) //activityClassname在QtLoader.loadApplication中被设置为QtActivity if (elements[it].getClassname().equals(activityClassname)) { stackDeep = it; break; } } if (-1 == stackDeep) return result; final String methodname=elements[stackDeep].getmethodname(); if (!m_delegateMethods.containsKey(methodname)) return result; //从m_delegateMethods表中查找要调用的函数,并执行 for (Method m : m_delegateMethods.get(methodname)) { if (m.getParameterTypes().length == args.length) { result.methodReturns = invokeDelegateMethod(m, args); result.invoked = true; return result; } } return result;}
invokeDelegate
根据被代理函数的名字,查表调用了代理函数。
看下m_delegateMethods
的赋值:
//QtLoader.loadApplication中调用到:QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);//m_delegateClass = QtActivity.class//qtLoader = new QtActivityDelegatepublic static voID setQtContextDelegate(Class<?> clazz, Object Listener){ m_delegateObject = Listener;//代理类设为QtActivityDelegate activityClassname = clazz.getCanonicalname();//activityClassname设为QtActivity //反射获取QtActivityDelegate的方法 ArrayList<Method> delegateMethods = new ArrayList<Method>(); for (Method m : Listener.getClass().getmethods()) { if (m.getDeclaringClass().getname().startsWith("org.qtproject.qt5.androID")) delegateMethods.add(m); } //反射获取QtApplication的字段 ArrayList<FIEld> applicationFIElds = new ArrayList<FIEld>(); for (FIEld f : QtApplication.class.getFIElds()) { if (f.getDeclaringClass().getname().equals(QtApplication.class.getname())) applicationFIElds.add(f); } //关联代理方法 //1. 关联到m_delegateMethods表 //2. 关联到QtApplication的字段,如Method onKeyUp for (Method delegateMethod : delegateMethods) { try { clazz.getDeclaredMethod(delegateMethod.getname(), delegateMethod.getParameterTypes()); //1. 关联到m_delegateMethods表 if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getname())) { QtApplication.m_delegateMethods.get(delegateMethod.getname()).add(delegateMethod); } else { ArrayList<Method> delegateSet = new ArrayList<Method>(); delegateSet.add(delegateMethod); QtApplication.m_delegateMethods.put(delegateMethod.getname(), delegateSet); } //2. 关联到QtApplication的字段,如Method onKeyUp for (FIEld applicationFIEld:applicationFIElds) { if (applicationFIEld.getname().equals(delegateMethod.getname())) { try { applicationFIEld.set(null, delegateMethod); } catch (Exception e) { e.printstacktrace(); } } } } catch (Exception e) { } }}
加了注释应该比较容易理解作者的意图了。这里交叉对比了QtActivity
和QtAcitivtyDelegate
的方法,然后构建了一个“代理方法表”,以方便在全局范围内,通过QtApplication.invokeDelegate
调用。
现在,继续之前的退出流程分析。
onStop
QtActivity.onStop被QtActivityDelegate.onStop代理:
//QtActivityDelegate.onStoppublic voID onStop(){ QtNative.setApplicationState(ApplicationSuspended);//ApplicationSuspended=0}//QtNative.setApplicationStatepublic static voID setApplicationState(int state){ synchronized (m_mainActivityMutex) { switch (state) { case QtActivityDelegate.ApplicationActive: m_activityPaused = false; Iterator<Runnable> itr = m_lostActions.iterator(); while (itr.hasNext()) runAction(itr.next()); m_lostActions.clear(); break; default: m_activityPaused = true; break; } } updateApplicationState(state);//我们关注下这个}public static native voID updateApplicationState(int state);
跟native代码:
//androIDjnimain.cpp/*enum ApplicationState { ApplicationSuspended = 0x00000000, ApplicationHIDden = 0x00000001, ApplicationInactive = 0x00000002, ApplicationActive = 0x00000004 };*/static voID updateApplicationState(jnienv */*env*/, jobject /*thiz*/, jint state){ …… if (state == Qt::ApplicationActive) QtAndroIDPrivate::handleResume(); else if (state == Qt::ApplicationInactive) QtAndroIDPrivate::handlePause(); if (state <= Qt::ApplicationInactive) { if (QAndroIDEventdispatcherStopper::instance()->stopped()) return; QAndroIDEventdispatcherStopper::instance()->goingToStop(true); QwindowsystemInterface::handleApplicationStateChanged(Qt::ApplicationState(state)); if (state == Qt::ApplicationSuspended)//onStop会触发这个分支 QAndroIDEventdispatcherStopper::instance()->stopAll(); } else { …… }}
这里主要调用了QAndroIDEventdispatcherStopper::instance()->stopAll();
,native代码不是重点,不深入分析QAndroIDEventdispatcherStopper
了。这里stopAll会导致qt main函数中QtApplicaiton.exec循环退出。
循环退出后,qt主线程:
static voID *startMainMethod(voID */*data*/){ …… int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data())); …… sem_post(&m_terminateSemaphore); sem_wait(&m_exitSemaphore);//在这里等待信号量 sem_destroy(&m_exitSemaphore); // We must call exit() to ensure that all global objects will be destructed exit(ret); return 0;}
“主函数”退出后,会发出m_terminateSemaphore
信号,并等待m_exitSemaphore
信号。直到m_exitSemaphore
信号到来前,线程还不会退出。
onDestroy
QtActivity.onDestroy被QtActivityDelegate.onDestroy代理:
//QtActivityDelegate.onDestroypublic voID onDestroy(){ if (m_quitApp) { QtNative.terminateQt(); QtNative.setActivity(null, null); if (m_deBUGgerProcess != null) m_deBUGgerProcess.destroy(); System.exit(0);// FIXME remove it or find a better way }}//QtNative.terminateQtpublic static native voID terminateQt();
跟native代码:
//androIDjnimain.cppstatic voID terminateQt(jnienv *env, jclass /*clazz*/){ // QAndroIDEventdispatcherStopper is stopped when the user uses the task manager to kill the application if (!QAndroIDEventdispatcherStopper::instance()->stopped()) { sem_wait(&m_terminateSemaphore);//等待m_terminateSemaphore sem_destroy(&m_terminateSemaphore); } …… if (!QAndroIDEventdispatcherStopper::instance()->stopped()) { sem_post(&m_exitSemaphore);//发送m_exitSemaphore pthread_join(m_qtAppThread, nullptr); }}
先等待m_terminateSemaphore
信号量,然后发送m_exitSemaphore
信号量。和startMainMethod
的退出相辅相成。
至此,退出流程也分析完了。
总结
QtActivity使用singletop模式QtAcitivity的onDestroy是退出点,但qt主线程的退出是由onStop触发的QtActivity的生命周期处理是用代理的方式,转变为Qt应用的生命周期处理其他
上面分析的启动、退出流程只是抛砖引玉。
读者可以借此分析更多,比如:
跟踪qt应用的deBUG实现分析其他生命周期的实现,比如pause/resume学习java反射的用法在启动和退出流程中hook基于AndroID的模板工程,使用AndroID SDK添加不方便qt实现功能Service
敏锐的读者已经发现,除了基于Activity作为qt应用的"容器"外,还有另一条支线——用Service作为”容器“。(QtService、QtServiceLoader、QtServiceDelegate)
感兴趣的读者可以分析看看。
=================== End
总结
以上是内存溢出为你收集整理的Qt android 浅析全部内容,希望文章能够帮你解决Qt android 浅析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)