最近在用Qt開發Android應用時需要獲取https頁面內容,但Qt內置的QNetworkAccessManager類只支持下面這些協議(調用其supportedSchemes成員函數獲取):
("ftp", "file", "qrc", "http", "data")
而網上我找到的支持https的介紹是使用QSslConfiguration類,然后把OpenSSL的兩個DLL(libeay32.dll和ssleay32.dll)復制到Qt庫目錄中,但我始終沒成功,也就懶得在手機上折騰了。
這個思路不行,還有兩個方案:一是通過QAndroidJniEnvironment和QAndroidJniObject調用Android SDK中封裝的Https訪問代碼,還有就是通過cURL庫,可以復用以前的代碼,而且性能也不錯,所以選擇這個方案。
首先是准備工作:
- Windows(Win10 x64)
- 下載並安裝msys2(http://repo.msys2.org/distrib/x86_64/),啟動msys2_shell.cmd腳本,運行“pacman -Syuu”升級后關閉控制台窗口,重新啟動后再運行一遍。這一步是可選的,如果不更新應該也可以,但我是更新后開始下一步的。
- 下載Windows版NDK並安裝
- 設置ANDROID_NDK_HOME環境變量:exprot ANDROID_NDK_HOME=/d/android-ndk-r20
- 更新PATH環境變量:export PATH="/d/android-ndk-r20/toolchains/llvm/prebuilt/windows-x86_64/bin/":$PATH
- Linux(CentOS 8)
- 下載Linux版NDK並安裝
- 設置ANDROID_NDK_HOME環境變量:exprot ANDROID_NDK_HOME=/home/user/android-ndk-r20
- 更新PATH環境變量:export PATH="/home/user/android-ndk-r20/toolchains/llvm/prebuilt/linux-x86_64/bin":$PATH
- 下載OpenSSL源碼包,版本必須是1.1.x,開始我沒注意,用的是之前下載的1.0.x,折騰了很長時間也沒搞定,郁悶!
- 下載cURL源碼包,我用的版本是7.66.0
然后就可以開工,下面列出的命令都是Linux平台,Windows里面msys2的命令大同小異。
生成OpenSSL
- cd openssl-1.1.1d
- ./Configure shared android-arm -D__ANDROID_API__=23 no-asm no-ssl2 no-ssl3 no-comp no-hw no-engine --prefix=/usr/local/ssl
- make -j && make install
上面第二行命令中的android-arm參數要注意,腳本提示可選的系統/編譯器有很多:
android-arm android-arm64 android-armeabi android-mips android-mips64 android-x86 android-x86_64 android64 android64-aarch64 android64-mips64 android64-x86_64
但要選擇哪個取決於你所用NDK根目錄下“platforms/android-xx”里面的子目錄能對應上才行,例如android-ndk-r20中的android-16里面只有arch-arm和arch-x86,所以如果命令行設置__ANDROID_API__=16的話,是不能生成android-arm64版庫文件的。
接下來是cURL
- cd curl-7.66.0
- CC=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang ./configure --prefix=/usr/local/curl --host=arm-linux-androideabi --with-ssl=/usr/local/ssl/
- make -j && make install
在用到Qt項目之前,先寫個控制台程序測試一下:
1 #include <iostream> 2 #include <map> 3 #include <string> 4 #include <curl/curl.h> 5 6 7 using namespace std; 8 9 10 #define URL u8R"(https://raw.githubusercontent.com/LCTT/LFS-BOOK/9.0-translating/README.md)" 11 12 13 typedef std::map<std::string, std::string> HeaderFields; 14 typedef struct { 15 int code; 16 std::string body; 17 HeaderFields headers; 18 } Response; 19 20 21 size_t write_callback( void *data, size_t size, size_t nmemb, void *userdata ) 22 { 23 Response *r = reinterpret_cast< Response * >( userdata ); 24 r->body.append( reinterpret_cast< char * >( data ), size * nmemb ); 25 return size * nmemb; 26 } 27 28 29 int _cURL() 30 { 31 if( curl_global_init( CURL_GLOBAL_ALL ) != CURLE_OK ) 32 { 33 cout << "Call curl_global_init failed! \n"; 34 return 1; 35 }// if 36 37 CURL *curlHandle = curl_easy_init(); 38 CURLcode res = CURLE_OK; 39 curl_slist *headerList = nullptr; 40 Response ret = {}; 41 curl_easy_setopt( curlHandle, CURLOPT_URL, URL ); 42 curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, write_callback ); 43 curl_easy_setopt( curlHandle, CURLOPT_WRITEDATA, &ret ); 44 curl_easy_setopt( curlHandle, CURLOPT_SSL_VERIFYPEER, 0 ); 45 curl_easy_setopt( curlHandle, CURLOPT_SSL_VERIFYHOST, 0 ); 46 res = curl_easy_perform( curlHandle ); 47 if( res != CURLE_OK ) 48 cout << "The result code is: " << ( int )res << "\n"; 49 if( false == ret.body.empty() ) 50 cout << ret.body << "\n"; 51 curl_easy_cleanup( curlHandle ); 52 curl_global_cleanup(); 53 54 return 0; 55 } 56 57 58 int main() 59 { 60 return _cURL(); 61 }
保存為test.cc,然后生成Android控制台程序:
$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang++ test.cc -I/usr/local/curl/include -Wl,-Bstatic -lcurl -lssl -lcrypto -Wl,-Bdynamic -o test
注意curl、ssl和crypto三個庫用-Wl,-Bstatic方式指定鏈接靜態庫(順序不能錯),否則默認會鏈接動態庫,但非root手機沒有復制動態庫到系統目錄的權限,所以需要靜態鏈接得到test,然后開啟Android手機的調試模式並連接主機USB,最后在命令行切換到Android SDK的platform-tools目錄:
- 把文件復制到手機存儲器:adb push test /data/local/tmp
- 啟動adb Shell:adb shell
- 文件添加可執行權限:chmod +x /data/local/tmp/test
- 啟動:/data/local/tmp/test
順利的話就可以正確獲取並顯示頁面中的文本,然后就可以導入Qt項目,首先把cURL的頭文件都復制到項目的目錄中,然后把libcurl.a、libssl.a和libcrypto.a三個庫文件復制到項目路徑的android/lib中,修改*.pro文件,添加下面一行:
LIBS += -L$$PWD/android/lib -lcurl -lssl -lcrypto
如果已經有LIBS就在后面加上-lcurl -lssl -lcrypto三個庫的引用,最后即可生成APK。
注意:
- 如果系統沒有GNU binutils,運行cURL的configure腳本前需要把NDK的路徑添加到PATH,而且是添加對應生成目標的路徑,例如要生成64位arm指令集:
PATH=“$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/bin/”:$PATH
然后在生成不同目標平台庫的時候先切換,例如通用arm指令集:
PATH=“$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/arm-linux-androideabi/bin/”:$PATH
- Qt應用項目的Android系統版本號要大於等於生成cURL和OpenSSL設置的__ANDROID_API__版本號,否則鏈接時可能會出現找不到stdin、stdout和stderr外部符號的錯誤,一般生成庫都是選個低點的系統版本兼容性更好。