天天看點

Android O 源碼中編譯程式, 關于32/64位so庫相容問題的記載坑1:坑2坑3

由于項目需求,需要在Android O系統中加入第三方庫, Android O 預設編譯的是64位作業系統, 第三方公司提供了32位和64位的庫, 但是在實際應用過程中還是遇到了種種問題, 在此做個記錄, 希望遇到同樣問題的小夥伴們不要掉入相同的坑.

具體分為以下幾個問題:

  1. 系統是怎樣判斷一個應用是32/64位架構
  2. 如何在源碼中将自己寫的應用編譯成32/64位
  3. 系統級應用如何使用apk自身的庫檔案
  4. android預設的壓縮優化引發的問題

坑1:

項目一開始, 客戶隻提供了32位的庫檔案, 将自己寫的應用和庫檔案內建到系統, Android.mk檔案如下:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_APPS)

LOCAL_PACKAGE_NAME := OemScanDemoTest
#LOCAL_CERTIFICATE := platform

#LOCAL_JACK_ENABLED := disabled
LOCAL_MULTILIB := 32

include $(BUILD_PACKAGE)

include $(CLEAR_VARS)
LOCAL_MODULE := libHHPScanInterface
LOCAL_SRC_FILES := libHHPScanInterface.so
LOCAL_MULTILIB := 32
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_SUFFIX := .so
include $(BUILD_PREBUILT)

include $(CLEAR_VARS)
LOCAL_MODULE := libHSMDecoderAPI
LOCAL_SRC_FILES := libHSMDecoderAPI.so
LOCAL_MULTILIB := 32
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_SUFFIX := .so
include $(BUILD_PREBUILT)

include $(CLEAR_VARS)
LOCAL_MODULE := libHsmKil
LOCAL_SRC_FILES := libHsmKil.so
LOCAL_MULTILIB := 32
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_SUFFIX := .so
include $(BUILD_PREBUILT)
           

在代碼中肯定是會load這些庫檔案的, 編譯好之後燒錄到系統驗證, 結果報錯如下:

Android O 源碼中編譯程式, 關于32/64位so庫相容問題的記載坑1:坑2坑3

這就很奇怪, 明明我的Android.mk中配置了LOCAL_MULTILIB := 32屬性, 按理來說不應該是32位的應用嗎? 為什麼在lib64檔案夾下找庫檔案? 于是檢視了下zygote程序, 我們的程式果然是zygote64派生的, 說明系統采用了64位的架構來解析我們的應用

msm8953_64:/ # ps -A | grep zygote
root           862     1 4246428  75192 poll_schedule_timeout 736113afa8 S zygote64
root           865     1 1577004  67052 poll_schedule_timeout f398c778 S zygote
webview_zygote 2022    1 1339532  32908 poll_schedule_timeout f2612778 S webview_zygote32
msm8953_64:/ # 
msm8953_64:/ # ps -A | grep 862                                                                                                                                                  
root           862     1 4246428  75192 poll_schedule_timeout 736113afa8 S zygote64
.....
u0_a78        3765   862 4327188  50488 binder_thread_read 736113af78 S com.example.oemscandemo
           

使用ps -A | grep 865 檢視32位zygote派生的所有程序, 發現還是有程式是32位的, 說明并不是所有的系統應用都強制要求是64位的. 那麼, 系統是根據什麼條件判斷一個應用該采用哪種架構運作呢? 這裡有一篇部落格寫的很好:

https://blog.csdn.net/weixin_40107510/article/details/78138874

借用部落格中的總結:

  1. 如果apk包中lib檔案夾下有.so庫,就根據這個.so庫的架構模式,确定app的primaryCpuAbi的值
  2. 對于system app, 如果沒法通過第一步确定primaryCpuAbi的值,PKMS會根據/system/app/${APP_NAME}/lib和/system/app/${APP_NAME}/lib64這兩個檔案夾是否存在,來确定它的primaryCpuAbi的值
  3. 對于還沒有确定的app, 在最後還會将自己的primaryCpuAbi值與和他使用相同UID的package的值設成一樣
  4. 對于到這裡還沒有确認primaryCpuAbi的app,就會在啟動程序時使用ro.product.cpu.abilist這個property的值的第一項作為它關聯的ABI

由于我們的是系統級的app, 庫檔案都是編譯到system/lib/ 或 vendor/lib等目錄下, apk自身不存在lib檔案夾, 導緻最終會走到上面提到的四點中的最後一點, 系統屬性ro.product.cpu.abilist 第一項為 arm64-v8a, 是以我們的程式就光榮的成為了64位應用.

坑2

既然你一定要跟我對着幹, 一定要使用64位的庫, 那就使用64位的庫好了, 還好客戶提供了相應的64位的庫檔案. 于是将32位的庫替換成64位的, 滿以為問題可以得到圓滿解決, 但人生中很多時候, 願望和顯示是相悖的, 運作後還是崩潰!

Android O 源碼中編譯程式, 關于32/64位so庫相容問題的記載坑1:坑2坑3

提示找不到的這個庫是系統中camera的底層庫檔案, 系統自帶的, 因為客戶提供的庫中需要load這個庫檔案(掃碼庫, 需要打開相機). 而且! 它隻能編譯成32位看庫檔案! Oh shit!

如果強行将libcamera_interface.so編譯成64位的, 很有可能引起系統異常, 這也不符合程式設計的基本思想: 可以擴充添加代碼, 盡量不要修改原有代碼. 更何況這是系統的底層代碼庫啊, 再退一步講, 如果這個庫又引用了其它的庫檔案呢? 這樣改下去将變成一個巨大的工程.

繞來繞去還是繞回來了, 還得使用32位的庫. 那麼如何将一個系統級應用編成一個32位程式呢? 根據坑1 的介紹, 隻要在應用的目錄下出現lib/armeabi-v7a而不是lib/arm64-v8a的檔案夾, 應該就可以讓系統認為是32位架構的程式了, 于是修改了Android.mk:

include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_APPS)

LOCAL_PACKAGE_NAME := OemScanDemoTest
LOCAL_CERTIFICATE := platform
LOCAL_MULTILIB := 32

<!-- 修改了這裡 -->
LOCAL_JNI_SHARED_LIBRARIES := libHHPScanInterface libHSMDecoderAPI libHsmKil libmmcamera_interface libbinder libcamera_client libcutils libutils libc++

include $(BUILD_PACKAGE)
           

編譯生成的産物目錄下産生了lib/armeabi-v7a檔案夾, 按照坑1中的理論, 這個應用會被zygote派生, 而不是zygote64. 燒錄驗證, 果然不再報找不到庫檔案的錯誤, 使用ps -A | grep zygote指令檢視, 我們的應用程序也确實是32位zygote派生, 這就說明我們的應用徹徹底底的成為了一個32位應用.

其中LOCAL_MULTILIB := 32 的作用是在應用目錄下生成lib/armeabi-v7a, LOCAL_JNI_SHARED_LIBRARIES 作用是将.so檔案打包到lib/armeabi-v7a檔案夾下, 這樣一來, 雖然是系統應用, 也不用去system/lib 或者 vendor/lib下去找庫檔案, 優先使用自身庫檔案.

坑3

問題到了這兒還沒完, 坑永遠填不完, 這回不再報找不到庫的錯誤, 運作報錯如下:

Android O 源碼中編譯程式, 關于32/64位so庫相容問題的記載坑1:坑2坑3

庫是找的到了, 調用庫裡面的方法出問題了! 不得不懷疑是這個庫給的有問題, 但是用Android Studio編譯, 并且把selinux權限臨時關閉掉(adb shell setenforce 0), 安裝到系統是可以正常使用的. (使用IDE編譯應用能夠使用為什麼還要在系統裡去編譯呢? 其一, 由于需求原因, 我們需要将代碼内置到系統, 作為一個系統服務, 友善其它程序調用; 其二, 第三方程序SELinux權限也不好解決, 有的甚至無法解決, 是以我們必須要将代碼在源碼中編譯, 并添加好相應的SELinux權限. 關于權限問題, 不是本篇主題, 不做介紹).  這樣看來不是庫的問題, 那會不會是編譯器的不同導緻的呢?

我們知道android8.x的源碼使用的是jack編譯器, android studio 使用的什麼編譯器我沒有了解過, 但是Android.mk中有一條屬性可供配置: LOCAL_JACK_ENABLED := disabled, 配置這個屬性是告訴系統這個程式不采用jack編譯器編譯, 使用老編譯器編譯. 燒錄系統測試, 還是一樣的錯誤.

熟悉C的人看到這種錯誤肯定很熟悉, 是因為庫的參數不相容或者調用這傳入的參數和.so中的參數不比對導緻的. 網上找了很久的資料結果無所獲. 猛然記起, Android有一個壓縮優化的屬性可供配置(貌似以前遇到過類似的坑, 由于沒做筆記時間一長又被坑一次):

LOCAL_PROGUARD_ENABLED := disabled  禁用混淆

LOCAL_DEX_PREOPT := false   關閉優化

而android源碼中預設是打開LOCAL_DEX_PREOPT屬性的, 優化了so檔案, 導緻so庫裡面的jni函數接口産生了變化, 于是就出現了上面的錯誤. 關閉優化, 将SELinux權限問題處理掉, 終于可以正常使用了.

還有些細節也記錄一下, 原本打算将應用編譯到system/app/目錄下的, 但是有些庫檔案, 比如libmmcamera_interface.so, 隻存在vendor/lib/目錄下. 這個時候LOCAL_JNI_SHARED_LIBRARIES := libmmcamera_interface 會報錯, 說找不到這個庫檔案. 也就是說system下的程序不能引用vendor下的資源. 然後将應用編譯進了vendor/app/目錄下, libHHPScanInterface libHSMDecoderAPI libHsmKil libmmcamera_interface libbinder libcamera_client libcutils libutils libc++ 這個屬性卻不會報錯了, 而事實上有些庫是存在system/lib/下的. 于是得出了一個來不及細想的結論:

vendor/是Google用來作為營運商和廠商做私有定制的目錄,  但也是系統級别的, 是以它可以調用到system下的資源; 而反過來, Android O 之後的系統, system由Google完成, 按照Google的預想, 客戶隻需要定制vendor即可, 不需要廠商再來操心system. 這麼一想, system不能使用vendor下的資源也就可以了解了, 因為Google根本不需要使用你一個第三方的東西.

當然, 對于這個問題我沒有去仔細研究,  以上隻是我的推測, 如有研究過的朋友, 可以分享.

繼續閱讀