本文主要用于总结之前分析QT For Android 从启动到QWidget启动的相关代码,可以通过该文大概知晓QT是如何对Android平台进行支持的。
话不多说,直接进入正文。
通过Qt For Android 开发的应用说到底,其本质依旧是一个Android 的APP。而传统的Android是通过Java进行开发的,而且我们都知道绝大部分的Android App是由一系列的Activity组成的,Activity可以认为是Android App的入口。而QT 也是通过Activity来作为连接QT和Android的桥梁。
通过查看QT For Android 的AndroidManifest.xml(该文件在:qtbase\src\android\templates中),我们可以知道QT For Andoird应用的主Activity是QtActivity。
qtbase\src\android\java\src\org\qtproject\qt5\android\bindings\QtActivity.java
...
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);
}
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
onCreateHook(savedInstanceState);
}
...
通过该Activity的onCreate函数,我们可以知道,QtActivity将onCreate的工作交给m_loader.onCreate函数,m_loader是一个QtActivityLoader对象。
qtbase\src\android\java\src\org\qtproject\qt5\android\bindings\QtActivityLoader.java
public void onCreate(Bundle savedInstanceState) {
...
startApp(true);
}
该onCreate函数没有什么特别的,其主要是做一些环境和主题的初始化,而后便是调用startApp来进行相关动态库的加载。该startApp的实现在QtActivityLoader的父类QtLoader。
qtbase\src\android\java\src\org\qtproject\qt5\android\bindings\QtLoader.java
public void startApp(final boolean firstStart)
{
...
loaderParams.putString(LOADER_CLASS_NAME_KEY, loaderClassName()); // (1) 获取QT Loader的类名
...
loadApplication(loaderParams); // (2) 加载相关库
...
}
在QtLoader的starApp中也没有做太多有意义的事情,主要是获取AndroidManifest配置文件中的相关依赖库,并且将相关库的列表放入loaderParams中,其中比较关键的代码如下:
(1) 通过loaderClassName() 函数来获取QT Loader的类名 而后调用loadApplication函数。该函数的实现如下(该函数实现在QtActivityLoader中):
qtbase\src\android\java\src\org\qtproject\qt5\android\bindings\QtActivityLoader.java
protected String loaderClassName() {
return "org.qtproject.qt5.android.QtActivityDelegate";
}
(2)调用loadApplication(loaderParams) 来加载相关类,其具体实现如下:
qtbase\src\android\java\src\org\qtproject\qt5\android\bindings\QtLoader.java
private void loadApplication(Bundle loaderParams){
Class<?> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // (3)通过java反射机制加载以LOADER_CLASS_NAME_KEY为名的类
Object qtLoader = loaderClass.newInstance(); // 实例化出一个对象
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
contextClassName(),
ClassLoader.class,
Bundle.class); // (4)获取对象的loadApplication函数
if (!(Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams)) // (5) 调用函数loadApplication函数
throw new Exception("");
Method startAppMethod=qtLoader.getClass().getMethod("startApplication"); // (6) 获取对象的startApplication函数
if (!(Boolean)startAppMethod.invoke(qtLoader)) // (7) 调用startApplication函数
throw new Exception("");
}
上述loadApplication函数主要的工作如下:
(3)加载LOADER_CLASS_NAME_KEY对应的类,并调用其loadApplication函数。而我们从上文可以知道,LOADER_CLASS_NAME_KEY对应的类名为org.qtproject.qt5.android.QtActivityDelegate。
(4,5)获取该类中的loadApplication函数并调用,该函数主要是负责加载相关库,本文不做深入分析。
(6,7)获取该类中的startApplication函数并调用,该函数大部分也是在做初始化相关的动作,最后变会调用一个关键的函数QtActivityDelegate的onCreate函数
public void onCreate(Bundle savedInstanceState)
{
/**
* (8)创建runable
*/
Runnable startApplication = null;
if (null == savedInstanceState) {
startApplication = new Runnable() {
@Override
public void run() {
try {
String nativeLibraryDir = QtNativeLibrariesDir.nativeLibrariesDir(m_activity);
QtNative.startApplication(m_applicationParameters,
m_environmentVariables,
m_mainLib,
nativeLibraryDir);
m_started = true;
} catch (Exception e) {
e.printStackTrace();
m_activity.finish();
}
}
};
}
m_layout = new QtLayout(m_activity, startApplication); // (9) 将创建的runable用来构建QtLayout
try {
ActivityInfo info = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), PackageManager.GET_META_DATA);
if (info.metaData.containsKey("android.app.splash_screen_drawable")) {
m_splashScreenSticky = info.metaData.containsKey("android.app.splash_screen_sticky") && info.metaData.getBoolean("android.app.splash_screen_sticky");
int id = info.metaData.getInt("android.app.splash_screen_drawable");
m_splashScreen = new ImageView(m_activity); // (10) 创建启动屏,实际上就是一个
m_splashScreen.setImageDrawable(m_activity.getResources().getDrawable(id));
m_splashScreen.setScaleType(ImageView.ScaleType.FIT_XY);
m_splashScreen.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
m_layout.addView(m_splashScreen); // (11) 添加进activity的content layout中
}
} catch (Exception e) {
e.printStackTrace();
}
...
m_activity.setContentView(m_layout,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)); // (12) 将创建的QtLayout设置为Activity的COntentView
...
}
在QtActivityDelegate的onCreate函数中:
(8,9) 首先创建了一个关键的Runable,并且将其传递给了QtLayout的构造函数。
(10,11)接着就是判断AndroidManifest.xml文件中是否有指定splash_screen,也就是启动屏,如果有就会将该启动splash_screen(实际上就是一个ImageView)添加进刚刚创建的m_layout中。于是Activity启动的时候便会先显示我们在AndroidManifest.xml文件中指定的图片。
(12)最后并且(8,9)创建的QtLayout设置为Activity的contentview。而且个刚刚创建的Runable会在QLayout的onSizeChanged事件中被调用。而这个事件是Activity显示过中发生的。
至此,Qt For Android中的QtActivity的onCreate函数便分析完成了。可是看到这里,仍然没有看到任何和Qt相关的东西,Qt的界面究竟是如何显示在Android上的呢?那就得看接下来的分析。
当QtActivity显示出来后,就会调用我们在上文中说的Runable了。而这个Runable只是调用了QtNative类中的startApplication。
public static boolean startApplication(String params,
String environment,
String mainLibrary,
String nativeLibraryDir) throws Exception
{
File f = new File(nativeLibraryDir + "lib" + mainLibrary + ".so");
if (!f.exists())
throw new Exception("Can't find main library '" + mainLibrary + "'");
if (params == null)
params = "-platform\tandroid";
boolean res = false;
synchronized (m_mainActivityMutex) {
res = startQtAndroidPlugin();
setDisplayMetrics(m_displayMetricsScreenWidthPixels,
m_displayMetricsScreenHeightPixels,
m_displayMetricsDesktopWidthPixels,
m_displayMetricsDesktopHeightPixels,
m_displayMetricsXDpi,
m_displayMetricsYDpi,
m_displayMetricsScaledDensity,
m_displayMetricsDensity);
if (params.length() > 0 && !params.startsWith("\t"))
params = "\t" + params;
startQtApplication(f.getAbsolutePath() + params, environment);
m_started = true;
}
return res;
}
这个函数及其的简单,设置了相关参数后,便调用了JNI函数,startQtApplication,从此进入了C++的世界。
static jboolean startQtApplication(JNIEnv *env, jobject /*object*/, jstring paramsString, jstring environmentString)
{
...
// Go home
QDir::setCurrent(QDir::homePath());
//look for main()
if (m_applicationParams.length()) {
// Obtain a handle to the main library (the library that contains the main() function).
// This library should already be loaded, and calling dlopen() will just return a reference to it.
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");
} else {
qWarning("No main library was specified; searching entire process (this is slow!)");
m_main = (Main)dlsym(RTLD_DEFAULT, "main");
}
...
return 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())); // 执行QT的main函数
sem_post(&m_terminateSemaphore);
sem_wait(&m_exitSemaphore);
sem_destroy(&m_exitSemaphore);
exit(ret);
return 0;
}
该函数的主要目的就是在于,加载我们用户所编写的main函数。我们知道,在android中启动一个activity会创建一个进程,而这个进程是已经拥有mian函数的,其入口在java虚拟机创建时指定。于是,QT将用户编写的main函数放在了so中,然后通过上述代码,加载SO,并且执行其main函数,于是便彻底的进入了QT的世界。
后续将会分析QT是如何在android是显示QWidget的,QT的platform plugin到底是什么东西。
Qt android浅析: https://zhuanlan.zhihu.com/p/36798160 写得十分好的一篇文章