更新:
- 修改了一些邏輯錯誤
- 添加了一些圖示說明
這兩天弄了一下android相冊的相關功能。還是花了挺長時間的,這里總結一下,避免以后再踩坑。同時也在這篇文章里面補齊一些android開發的基礎支持
打開Android相冊並選一個圖片進行顯示
分為幾個步驟:
-
QtCreator新建Android工程
本例使用的是arm64-v8 Android開發套件。
-
構建工程並在構建目錄中找到AndroidManifest.xml
創建的Android工程build之后都會在android-build根目錄下生成一個AndroidManifest.xml文件。這個文件是android開發很重要個的一個文件,是應用清單。項目中引用的java包、app的橫屏和豎屏、app的是否全屏等等很多功能都是在里面設置的。下面有一些詳細的參考文章:
-
在工程中添加AndroidManifest.xml和java文件。
這里需要詳細說一下Qt創建android工程的特殊步驟,如下圖所示:
創建android工程我們需要點擊上圖的按鈕來創建模板,創建好的工程目錄如下圖:
QtCreator會自動為我們創建如下一系列文件其中我們暫時用到的是AndroidManifest.xml文件。之后我們要把步驟2中提到的相同文件拷貝到該目錄下面做替換。同時我們生成的調用相冊方法的java文件也需要拷貝到這個目錄下面,存放的目錄結構如下:
那么這么一長串的目錄的創建依據是什么呢,就是我們的java文件里面定義的包名
package org.qtproject.example.OpenAndroidAlbum;
程序運行起來之后會根據目錄結構找到我們的java包。當然我們也可以自己在工程根目錄下創建android目錄,只放入java后綴的文件和AndroidManifest.xml文件。這個目錄的最終作用還是在程序構建的時候會把目錄下面的文件拷貝到我們的構建目錄下面,這里紅色框框里面的東西會被我們創建的工程目錄下的東西給替換掉,里面的一些文件具體是做什么的感興趣的小伙伴可以自己去研究一下,總之我們需要的主要就是兩個文件。
java文件是自己創建的用來寫一些java代碼調用android原生功能的相當於c++中的一個namespace的文件。
java文件可以從網上找一個來參考着寫。在文章的末尾我會附上gitee的地址供大家參考。
放出java的代碼簡單看一下吧:
public class OpenAndroidAlbum extends QtActivity { public static native void fileSelected(String fileName); static final int REQUEST_OPEN_IMAGE = 1; public String lastCameraFileUri; static final int REQUEST_CAPTURE_IMAGE = 2; private static OpenAndroidAlbum m_instance; public OpenAndroidAlbum() { m_instance = this; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void onDestroy() { super.onDestroy(); } static void openAnImage() { m_instance.dispatchOpenGallery(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { System.out.println("===dispatchOpenGallery1==="); if (resultCode == RESULT_OK) { if(requestCode == REQUEST_OPEN_IMAGE) { String filePath = getRealPathFromURI(getApplicationContext(), data.getData()); fileSelected(filePath); } } else { // fileSelected(":("); } super.onActivityResult(requestCode, resultCode, data); } private void dispatchOpenGallery() { Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); startActivityForResult(intent, REQUEST_OPEN_IMAGE); } public String getRealPathFromURI(Context context, Uri contentUri) { Cursor cursor = null; try { String[] proj = { MediaStore.Images.Media.DATA }; cursor = context.getContentResolver().query(contentUri, proj, null, null, null); int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); System.out.println(column_index); return cursor.getString(column_index); } finally { if (cursor != null) { cursor.close(); } } } }
-
fileSelected這個靜態函數是我們在c++代碼中定義的。java和c++的混合編程是通過JNI來實現的,
#ifdef Q_OS_ANDROID #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_org_qtproject_example_OpenAndroidAlbum_OpenAndroidAlbum_fileSelected(JNIEnv */*env*/, jobject /*obj*/, jstring results) { selectedFileName = QAndroidJniObject(results).toString(); qDebug() << "fileName = " << selectedFileName; } #ifdef __cplusplus } #endif #endif
名字看起來很長Java是固定頭部,org_qtproject_example_OpenAndroidAlbum這個是java包名,OpenAndroidAlbum是類名,最后fileSelected這個才是函數名。需要注意的是由於jni函數名映射成java函數名的時候是依靠“”來間隔包、類、方法的,如果你的函數中有“”字符的話,jni必須能夠區分函數名中的“_”是字符還是分隔符,所以在函數名前面需要加1用於區分。
由於JNI的函數是c函數,所以要加上
extern "C"
。這樣定義好之后的函數就可以在java中直接調用了,還是很方便的。 -
openAnImage是我們定義打開圖片按鈕的響應函數
java中定義的函數在c++中調用的方法是通過Qt的QAndroidJniObject類的一個靜態方法實現的:
QAndroidJniObject::callStaticMethod<void>("org/qtproject/example/OpenAndroidAlbum/OpenAndroidAlbum", "openAnImage", "()V");
第一個參數是類名其實也是包名,是在java文件中通過
package org.qtproject.example.OpenAndroidAlbum;
定義的。
-
dispatchOpenGallery這個方法用來調用相冊
通過Intent對象和startActivityForResult實現調用。這里有一個坑,
Intent intent = new Intent(Intent.ACTION_PICK);
創建對象的時候ACTION_PICK這個枚舉要用對,4.4以后的版本好像要用這個,我也是之前怎么都打不開相冊,改了這個枚舉之后就可以了。 -
onActivityResult在相冊中選中一張圖片之后會調用這個回調
很自然就會想到我們在c++中定義的fileSelected函數要在這個地方調用了。把路徑轉換成java的String類型,調用
fileSelected(filePath)
就可以在Qt代碼中處理圖片路徑了。
-
在Qt代碼中調用java的打開相冊的方法,同時利用JNI定義一個c++的處理方法
這個我們在上一條中也提到了。這里提一下編碼中容易出錯的地方
QAndroidJniObject::callStaticMethod<void>("org/qtproject/example/OpenAndroidAlbum/OpenAndroidAlbum", "openAnImage", "()V");
第一個字符串代表java包,相當於一個c++的類。如果遇到編譯過程中遇到“找不到類"的類似錯誤提示檢查一下第一個字符串,最后是以OpenAndroidAlbum.java包的前綴結尾的,切記!!!
-
AndroidManifest.xml要做相應的修改
完成了以上步驟還不算完,如果這時候直接執行程序會報錯:
JNI DETECTED ERROR IN APPLICATION: JNI GetStaticMethodID called with pending exception java.lang.NullPointerException: Attempt to invoke direct method 'void org.qtproject.example.OpenAndroidAlbum.OpenAndroidAlbum.dispatchOpenGallery()' on a null object reference
從錯誤提示上可以推斷出是調用了空對象的方法導致的。分析java代碼
m_instance.dispatchOpenGallery();
打開相冊的時候我們調用了這句話,而m_instance
我們是在public OpenAndroidAlbum() { m_instance = this; }
構造函數進行初始化的,我們調用的是靜態方法,確實沒有進行實例化。這里怎么解決呢?這就要用到兩個地方了,我們定義的java類繼承於QtActivity,而清單文件AndroidManifest.xml里面有一個activity標簽,這個標簽就是用來指定activity對應的類的,清單文件此時發揮了它”清單“的作用。此標簽介紹如下:
該元素聲明一個實現應用可視化界面的Activity(Activity類子類)。這是
元素中必要的子元素。所有Activity都必須由清單文件中的 元素表示。任何未在該處聲明的Activity對系統都不可見,並且 永遠不會被執行。 作者:閃電的藍熊貓
鏈接:https://www.jianshu.com/p/3b5b89d4e154
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。我們把activity標簽下的屬性 修改成
org.qtproject.example.OpenAndroidAlbum.OpenAndroidAlbum
即可。這個是不是很眼熟呢,沒錯就是我們在cpp中寫的QAndroidJniObject::callStaticMethod<void>("org/qtproject/example/OpenAndroidAlbum/OpenAndroidAlbum", "openAnImage", "()V");
第一個參數。這樣的對應關系保證沒錯的話就可以順利打開相冊了。
-
完成了上述5點之后只是能打開相冊而已,如果要讀取里面的圖片還要修改軟件的權限,獲取允許打開相冊的權限之后才能正常的打開相冊圖片
gitee地址:https://gitee.com/guiguzicom/Demo/tree/master/OpenAndroidAlbum