现在的位置: 首页 > 综合 > 正文

我眼中的Qt for Android

2013年01月31日 ⁄ 综合 ⁄ 共 6123字 ⁄ 字号 评论关闭

引子

        前几天,我分享了一下qt for android,从大家的反应和回馈,我看到两种极端的状态。一个是:“太好了!想做Android开发但是不想转java,这下不用了!” 另一个是:“不要在Qt上浪费时间了,它顶多在Android上跑个Hello world,别的什么也跑不了。”
       我先说说我对Qt for Android的客观认识。首先,从现有阶段看,不得不承认TA并不是一个成熟的技术(工具)。在大型项目中,还是不建议使用qt for android开发的,因为资料太少,我们无法快速深入的在大脑建立起qt for android网络,在遇到问题的时候,解决起来就很棘手。但是,绝不是说仅仅就能跑个Hello world,如果真的这么一无是处,TA就没有存在的意义,也就不会吸引大批开发者深入研究和优化了。要知道,世上最简单的事情就是批评和怒斥。我再次强调一下,我只是分享我所看到的知道的,不带任何向导性。对于技术本身,仁者见仁,智者见智

品味与探究

        当我看到这么一个技术工具,我的好奇心驱使我探究一下(1)TA到底是如何实现的,(2)程序在Android上执行效率和性能怎么样,(3)较常规的Android java开发和jni c++开发而言,两者之间有什么可以相互借鉴, (4)倘若Google真的开放纯c++开发,那么java和qt for android又是怎样的一番光景?

         一个开发者分享他某一个程序的设计思路:在Qt下通过jni得到java Env,从而使用GPS等android API,并且已经实现:

  1. JNIEnv *currEnv;  
  2. currEnv = 0;  
  3. if (currVM->AttachCurrentThread((void **)&currEnv, NULL)<0)  
  4. {  
  5.      emit error("Cannot attach the current thread to the VM");  
  6. }  

       也许因为我对Qt信号和槽的情有独钟,看到emit就感到很亲切,并且被深深的吸引了。那么从Qt for android 的qt工程源码看,到底是如何在android上成功启动并运行的呢?
     

启动流程分析

       用qt-creator创建的每个应用程序中,src下的文件都是基本相同。因为启动程序,创建接口,链接库,这些操作是每个应用程序所必需的,最初的qt程序被编译成了lib**.so的动态库,当调用JNI接口startQtApp函数时真正启动了qt程序。
      执行程序的入口在src/eu/licentia/necessitas/industrius/QtActivity.java中,onCreate调用startapp检查必要的库文件,扩展包,插件是否存在,并加载.链接ministro服务,得知ministro服务现在的状态,如果缺少qt库则需要借助ministro服务下载。

private void startApp(final boolean firstStart)
    {
        try
        {
            ActivityInfo ai=getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            if (!ai.metaData.containsKey("android.app.qt_libs_resource_id"))
            {
                // No required qt libs ?
                // Probably this application was compiled using static qt libs
                // or all qt libs are prebundled into the package
                m_ministroCallback.libs(null, null, null, 0, null);
                return;
            }
          
            int resourceId = ai.metaData.getInt("android.app.qt_libs_resource_id");
            m_qtLibs=getResources().getStringArray(resourceId);
           
                m_ministroCallback.libs(libs,"QT_IMPORT_PATH=/data/local/qt/imports\tQT_PLUGIN_PATH=/data/local/qt/plugins", 
                        "-platform\tandroid", 0,null);
                return;
        }
  
try {
    if(!bindService(new Intent(eu.licentia.necessitas.ministro.IMinistro.class.getCanonicalName()),
  m_ministroConnection, Context.BIND_AUTO_CREATE)) 
                    throw new SecurityException(""); 
        } catch (SecurityException e) { }

    }

   

    应用程序需要的库由AndroidManifest.xml中的qt_libs_resource_id 项指定,这一项来自于res/values/libs.xml中的qt_libs项。

  1. <?xmlversionxmlversion='1.0' encoding='utf-8'?>  
  2. <resources>  
  3.     <array name="qt_libs">  
  4.         <item>QtCore</item>  
  5.         <item>QtGui</item>  
  6.     </array>  
  7.     <arraynamearrayname="bundled_libs"/>  
  8. </resources>  
  9.    

qt应用程序的启动也是借助ministro服务,需要ministro提供相应的库支持,当得到相应的库后创建线程,启动startApplication。

 private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub(){                                            
        @Override                                                                                                          
        public void libs(finalString[] libs, final String evnVars, final String params,                                    
                         int errorCode, StringerrorMessage) throws RemoteException {                                       
            runOnUiThread(new Runnable() {                                                                                 
             @Override                                                                                                    
                public void run() {                                                                                        
                    startApplication(libs,evnVars, params);                                                                
                }                                                                                                          
            });                                                                                                            
        }                                                                                                                  
    };
 

在src/eu/licentia/necessitas/industriusQtApplication.java中 startApplication函数,先启动android的Plugin然后调用一个JNI接口startQtApp启动qt程序。
QtActivity.java中的startApplication函数会调用QtApplication.java中的 startApplication。

     public static void startApplication(String params, String environment)      
    {                                                                                  
        if (params == null)                                                          
            params ="-platform\tandroid";                                        
                                                                
        synchronized (m_mainActivityMutex)                                                               
        {                                                                                   
            startQtAndroidPlugin();      
            setDisplayMetrics(m_displayMetricsScreenWidthPixels,      
                            m_displayMetricsScreenHeightPixels,
                            m_displayMetricsDesktopWidthPixels,
                            m_displayMetricsDesktopHeightPixels,               
                            m_displayMetricsXDpi,                          
                            m_displayMetricsYDpi);
            if (params.length()>0)                                        
                params="\t"+params;                                         
            startQtApp("QtApp"+params,environment);                                                   
            m_started=true;                            
        }                                                              
    }
 

startQtApp时会启动线程startMainMethod,在startMainMethod线程中会将lib**.so中main函数入口以库函数接口的形式再次执行,恢复了qt可执行程序的本来面目,在后台执行
这部分源码在android-lighthouse的源码中。

JNI的部分代码


extern "C" int main(int, char **); //use the standard mainmethod to start the application
staticvoid * startMainMethod(void * /*data*/)
{
 
       char ** params;
       params=(char**)malloc(sizeof(char*)*m_applicationParams.length());
       for (inti=0;i<m_applicationParams.size();i++)
              params[i]=(char*)m_applicationParams[i].constData();
 
       int ret = main(m_applicationParams.length(),params);
       ......
}
 

重写onKeydown 

        通过上述调用过程可以知道,android程序是怎样通过JNI来调用qt库中的函数。其他相关android的的JNI接口在,plugins/platforms/android/下,比如 keydown。
在src/eu/licentia/necessitas/industriusQtActivity.java重写了onKeydown函数,调用JNI函数keyDown实现。
 
keyDown的实现在 plugins/platforms/android/mw/androidjnimain.cpp 中


static void keyDown(JNIEnv */*env*/, jobject /*thiz*/,jint key, jint unicode, jint modifier)
{
    ……
    int mappedKey=mapAndroidKey(key);
    if (mappedKey==Qt::Key_Close)
    {
        qDebug()<<"handleCloseEvent"<<mLastTLW;
        QWindowSystemInterface::handleCloseEvent(mLastTLW);
    }
    else
        QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, mappedKey,modifiers, QChar(unicode),true);
       //通过JNI的帮助,转化成了Qt的实现
}
 

结束语

先写到这吧,不管怎么说,接触qt for android ,让我收获了很多很多,绝对不仅限于qt 和 android领域。

抱歉!评论已关闭.