android qt 对比_Qt android浅析

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主线程的关系是什么样的?

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”,也就是指定有category.LAUNCHER的那个Activity,在模板工程中是QtActivity。

这样,主Activity的onCreate就不亚于第二入口:

@Override

public 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,易得, 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.getAbsolutePath() + 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(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.startApplicaiton

qt的主线程并不是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会先触发。

@Override

protected void onStop()

{

super.onStop();

QtApplication.invokeDelegate();//??}

@Override

protected 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

//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 delegateMethods = new ArrayList();

for (Method m : listener.getClass().getMethods()) {

if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android"))

delegateMethods.add(m);

}

//反射获取QtApplication的字段 ArrayList applicationFields = new ArrayList();

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 delegateSet = new ArrayList();

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 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(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)

感兴趣的读者可以分析看看。

你可能感兴趣的:(android,qt,对比)