Reprinted from:https://zhuanlan.zhihu.com/p/36798160
Qt5 supports writing Android applications.
Typicalmain
:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
This will display a blank window on Android devices.
but:
- Problem 1,
main
Function "conflict". We know that the Android process originates from the fork of zygote, and the function as the entry point of the processmain
has long been executed, so what about the entry function of Qtmain
in the above code? How is it executed? - Question 2, how is the life cycle of Android activity transformed into the life cycle of Qt?
- Question 3, what is the relationship between the Qt main thread and the Android main thread?
The following is based on Qt 5.10.1 analysis (source code referencehttps://github.com/qt/qtbase/releases/tag/v5.10.1).
helloworld engineering analysis
Create a new helloworld project in qtcreator. After compilation, you can see the directory structure in the qt build directory:
- android_build/
- libhelloworld.so
- main.o
- mainwindow.o
The .o file obviously corresponds to each cpp file, and the so file is a "collection" of .o files. After a simple inspection of the android-build directory, you can see that it is an android project organized by gradle.
So qt's Android support, in simple terms, is to generate an so file from the qt code we wrote, and finally generate an apk file through the automatically generated Android template project.
Take a look at build.gradle in android_build:
……
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']
}
}
……
In build.gradle, the default source code and resource path of the plug-in was adjusted throughsourceSets
. com.android.application
Main introduction completeqt5AndroidDir
memorysrc
, aidl
sumres
.
qt5AndroidDir
defininggradle.properties
:
qt5AndroidDir=/Users/xxxx/Qt5.10.1/5.10.1/android_armv7/src/android/java
generally points to android_armv7/src/android/java
. in the qt installation directory
Take a look at the libs directory:
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
Several core so copies required for qt operation are copied to the libs directory, so that they will be packaged into the final apk. AQtAndroid-bundled.jar
dependency is also introduced.
Summarize
- The code written in qt will be generated as a dynamic library named after the project name
- Qt's strategy for generating Android applications is to use a template project
- It is not difficult to guess that the running mode of qt application is that the Android application generated through the template project executes qt code in native calling mode.
Start process
From the analysis in the previous section, it can be seen that the Android code dominates the operation of the entire process. Then looking for the Qt application entrance starts with the Android code.
The entrance to Android applications is generally Application
, and in the template project android-build
, QtApplication
inherits Applicaiton
, but I did not find the place where libhelloworld.so
is loaded after browsing it, so I will skip it first.
Application
After is loaded, the "main Activity" will be executed, that is, the one <intent-filter>
specifies . In the template project it is . category.LAUNCHER
Activity
QtActivity
In this way, the main ActivityonCreate
is no less than the second entrance:
@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);
}
What you see herem_loader
is a classQtActivityLoader
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,这里是关键
}
Find a friendly function -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
Dealed with whether it is necessaryministro
to intervene in the Qt startup process.ministro
You can not embed the qt library in the application, but download it at runtime Necessary libraries. QtCreator
The apk generated by the created project comes with its own qt library, readers can ignore it ministro
.
FinallystartApp
calledloadApplication
- load the Qt library and run:
……
//这里上文分析了,加载到的是类是:org.qtproject.qt5.android.QtActivityDelegate
Class<?> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class
Object qtLoader = loaderClass.newInstance(); // create an instance
//反射调用loadApplication
Method 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);
//反射调用startApplication
Method startAppMethod=qtLoader.getClass().getMethod("startApplication");
if (!(Boolean)startAppMethod.invoke(qtLoader))
throw new Exception("");
This involves an important class in the qt Android package - delegate. For QtActivity
it isQtActivityDelegate
.
So far, I have come into contact with QtActivity, QtActivityLoader, and QtActivityDeleagate. These three classes are very important classes in their Android packaging. Pay attention to sorting out the relationship between the three classes.
startApp
The main job is to find the loaderClass (it is more appropriate to become a delegate), and then call its loadApplication
and startApplication
.
Delegate
loadApplication
is mainly used to read loadParams
and set some global values. It is not the focus of this article and will not be analyzed in depth.
startApplication
is the highlight:
//qtbase-5.10.1/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
public 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
'sstartApplication
calls its ownonCreate
, and onCreate
mainly creates a QtLayout
, as contentView
, and call the callback pointed to by the local variable when QtLayout onSizeChanged
. Then the startup task was thrown to .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);
Adjusted to the native method, implemented in 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()));
……
}
The startQtApplication
in the native code here locates the function in the qt code through dlsym
, and then creates a thread to execute the code of this function (so the qt main thread is not the main thread of Android). As for the function, we know that it will be executed until it exits. main
main
main
QApplication.exec()
At this point, libhelloworld.so generated by the qt project begins to execute the main function and begins to behave like a traditional desktop qt application.
Let’s sort it out.
Summarize
- The main startup process is QtActivity.create -> QtActivityLoader.startApp -> QtActivityDelegate.startApplication -> QtNative.startApplicaiton
- Qt's main thread is not Android's main thread, they are independent of each other
- qt's
main
function is not the entry function in the android application, it is just used as the calling entry point for the android code to start the qt code (in fact, main in the C language also has the same purpose)< /span> - There are many parameters for "starting" qt code left in AndroidManifest
Exit process
The entrance is found, look for the exit.
Traditional Qt applications will end the event loop after the main window is closed, thenQApplication.exec()
will exit, and the main function will exit.
The "window" created by qt in Android is actually a Surface in the Activity. And we know that Android Activity's life cycle onDestroy is considered exit. However, this is not always the case, because after onDestroy, the process can stay in the background and wait for the system to wake up.
So, how is qt's Android package connected?
First look at the launchMode of its QtActivity:android:launchMode="singleTop"
singleTop, in short: if it is already on the top of the stack, reuse it, otherwise, recreate it. This means that once QtActivity retreats behind other activities, it will need to be recreated the next time it returns to the top of the stack.
SoonDestroy
is a very reasonable "exit point". This corresponds to the previous onCreate
as the startup entry in the Android life cycle.
onDestroy
beforeonStop
Touch the party.
@Override
protected void onStop()
{
super.onStop();
QtApplication.invokeDelegate();//??
}
@Override
protected void onDestroy()
{
super.onDestroy();
QtApplication.invokeDelegate();//??
}
onStop and onDestroy can be done in one sentenceQtApplication.invokeDelegate
. It is a very elegant implementation, but it is a bit of an obstacle to the analysis process. Let’s take a look first.invokeDelegate
What have you done?
When analyzing other people's code, I like to guess the process.
For example, here, when analyzing the startup process, QtActivity was a shell. QtActivityLoader was called to help load libhelloworld.so, and the real processing occurred in QtActivityDelegate. This and QtApplication.invokeDelegate should not be just names. coincidence.
It is reasonable to guess that the task of QtApplication.invokeDelegate is to "elegantly" delegate what QtActivity needs to do to QtActivityDelegate.
Let’s take a look at this guess analysis.
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
According to the name of the proxy function, the proxy function is called by looking up the table.
look downm_delegateMethods
view:
//QtLoader.loadApplication中调用到:QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);
//m_delegateClass = QtActivity.class
//qtLoader = new QtActivityDelegate
public 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) {
}
}
}
It should be easier to understand the author's intention after adding comments. Here we cross-compare the methods of QtActivity
and QtAcitivtyDelegate
, and then build a "proxy method table" to facilitate global scope, through QtApplication.invokeDelegate
Call.
Now, continue with the exit process analysis from before.
onStop
QtActivity.onStop is proxied by QtActivityDelegate.onStop:
//QtActivityDelegate.onStop
public void onStop()
{
QtNative.setApplicationState(ApplicationSuspended);//ApplicationSuspended=0
}
//QtNative.setApplicationState
public 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);
Follow native code:
//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 {
……
}
}
The main calls here areQAndroidEventDispatcherStopper::instance()->stopAll();
. The native code is not the focus, so we will not analyze it in depthQAndroidEventDispatcherStopper
. Here stopAll will cause the QtApplicaiton.exec loop in the qt main function to exit.
After the loop exits, the qt main thread:
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;
}
After the "main function" exits, it will send out the m_terminateSemaphore
signal and wait for the m_exitSemaphore
signal. The thread will not exit until them_exitSemaphore
signal arrives.
onDestroy
QtActivity.onDestroy is proxied by QtActivityDelegate.onDestroy:
//QtActivityDelegate.onDestroy
public 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.terminateQt
public static native void terminateQt();
Follow native code:
//androidjnimain.cpp
static 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);
}
}
Wait for the semaphorem_terminateSemaphore
first, then send the semaphorem_exitSemaphore
. and startMainMethod
’s exit complement each other.
At this point, the exit process has been analyzed.
Summarize
- QtActivity uses singleTop mode
- QtAcitivity's onDestroy is the exit point, but the exit of the qt main thread is triggered by onStop
- The life cycle processing of QtActivity is transformed into the life cycle processing of Qt application using proxy.
other
The startup and exit processes analyzed above are just an introduction.
Readers can use this to analyze more, such as:
- Track the debug implementation of Qt applications
- Analyze the implementation of other life cycles, such as pause/resume
- Learn how to use java reflection
- Hook in the startup and exit process
- Android-based template project, using Android SDK to add inconvenient qt implementation functions
Service
Keen readers have discovered that in addition to the "container" based on Activity as a Qt application, there is another branch - using Service as a "container". (QtService, QtServiceLoader, QtServiceDelegate)
Interested readers can analyze it and take a look.