Qt android brief analysis

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, mainFunction "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 completeqt5AndroidDirmemorysrc, aidlsumres.

qt5AndroidDirdefininggradle.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.

ApplicationAfter is loaded, the "main Activity" will be executed, that is, the one <intent-filter> specifies . In the template project it is . category.LAUNCHERActivityQtActivity

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_loaderis 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("");
}

startAppDealed with whether it is necessaryministro to intervene in the Qt startup process.ministroYou can not embed the qt library in the application, but download it at runtime Necessary libraries. QtCreatorThe 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.

startAppThe 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.

startApplicationis 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 .startApplicationQtNative.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. mainmainmainQApplication.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'smain 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.

onDestroybeforeonStopTouch 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;
}

invokeDelegateAccording to the name of the proxy function, the proxy function is called by looking up the table.

look downm_delegateMethodsview:

//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.invokeDelegateCall.

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.

Guess you like

Origin blog.csdn.net/rankun1/article/details/86647188
Recommended