Classloader關於Apache Http在Android裏targetSdkVersion28前後時的表現調研

原因導向:項目在逐步適配targetSdkVersion,當Android P(9.0)手機問世後,出現了大面積的崩潰,後來定位到由於使用了org/apache/http/client/methods/HttpGet類,但是類缺失導致了crash。然後調研後發現Apache Http相關的類確實是在Android P版本的系統的rootclasspath去掉了,但是谷歌做了一個兼容:如果應用的targetSdkVersion<P,系統會把apache的類預加到應用自己的classpath中,所以應用自己的classloader還是能正常找到apache的類,但是如果應用指定使用系統的classpath加載apache的類就會有問題,會報這個異常(系統classpath獲取的指的是ClassLoader.getSystemClassLoader(),一般插件中代碼的預加載使用的就是系統的classloader)。

調研分析結論:
關於apache的問題,這裏分開來進行調研分析:
1、targetSdkVersion<28(P)時,Android28手機和小於28的手機
2、targetSdkVersion>=28(P)時,Android28手機和小於28的手機

針對應用(非插件化)targetSdkVersion和Android系統版本中是否包含Apache Http相關類結論:

targetSdkVersion / Android手機系統版本 應用ClassLoader是否包含Apache Http
targetSdkVersion<28 / >=Android P
targetSdkVersion<28 / <Android P
targetSdkVersion>28 / >=Android P
targetSdkVersion>28 / <Android P

Apache Http在Android 版本的變更:
1、Android API 23中已經開始不提供Apche Http相關的類,在項目中如果使用,需要在build.gradle中增加useLibrary ‘org.apache.http.legacy’,注意該配置只是爲了項目中編譯通過,不報錯。
2、Android P版本的系統的rootclasspath去掉了Apache Http相關的類,但是做了兼容,如果應用的targetSdkVersion<P,系統會把apache的類預加到應用自己的classpath中。

根據Apache Http的變更,我們會發現一個問題,即使我們編譯通過,當targetSdkVersion > P的時候在Android P以及P以上手機上是沒有這個類的,所以依然會報錯,這個時候需要在項目的AndroidManifest.xml文件中增加下面這個配置,可以保證運行正常

<uses-library android:name="org.apache.http.legacy" android:required="false"/>

針對插件使用Apache Http的摸索
首先一般插件使用的是系統的classloader,ClassLoader.getSystemClassLoader(),上面我們說了谷歌對targetSdkVersion小於28的應用,在28手機上系統會把apache的類預加到應用自己的classpath中,即應用自己的ClassLoader中是包含的,一般應用的classloader,使用context.getClassLoader()調用即可。
這裏我們針對系統ClassLoader和App的ClassLoader對插件和普通App的影響做一個對比。

1、插件和母體(普通應用,後面我們稱爲母包),排查方法如下

步驟(1)確認母包和插件的classLoader是否一致?
答:確認了不一致,插件使用的是系統的classloader,母包使用的是App的classLoader。

步驟(2)確認母包和插件classloader中是否存在 org/apache/http/client/methods/HttpGet?
答:
在Android P以下的手機上target<=28時母包和插件classloader是包含的(28以上的因爲還沒普及所以還沒適配);
在Android P手機上target<=27時母包存在,插件不存在;
在Android P手機上target>=28時,母包和插件均不存在;

步驟(3)根據以上步驟,嘗試母包和插件使用同一個classloader加載class(如何使用同一個classloader,使用上下文獲取到的classloader是應用一般的classloader mContext.getClassLoader()),會引申出以下問題
a.母包和插件不能有路徑相同且同名的class類,因爲同一個classloder只會爲相同的類創建一個內存空間,如果母包和插件中有路徑和類名完全一致的類就會導致只加載其中一個類。
b.插件和母包使用的公有變量都是一致的,不能單獨使用公有變量。
c.插件涉及到多個插件的時候需要規避以上兩點的地方會更多。
根據abc問題的解決方法是,避免插件和母包同路徑同名現象,母包和插件如果要單獨使用配置信息需要各自配置,不能使用公有的類

步驟(4)根據步驟2中確認在targetSdkVersion<28時候,母包classloader是存在 HttpGet的。因此嘗試在插件中也預先加載org相關的jar。

2、在插件中添加org apache相關的jar文件

方法(1)直接添加jar到插件中,類不報錯。但是有其他問題,會內聯導致崩潰,未解決問題
方法(2)在插件的classloader中預加載org.jar。

應用使用了apache中http的接口,依賴系統預製的apache的jar包:/system/framework/org.apache.http.legacy.boot.jar,
該jar包是通過系統的bootclassloader加載的,但是應用又在自己的插件dex中添加了同樣的jar包,觸發插件的時候就會出現
apache的類被內聯,又有跨dex的問題,導致應用閃退

調研問題總結:
調研發現google在targetSdkVersion = 23開始就去掉了org裏面一些類,在編譯時就會報錯,對此google在android studio中給出的解決辦法是在相應的module下的build.gradle中加入:

android {
    useLibrary 'org.apache.http.legacy'
}

此方法是爲了讓編譯通過。
其實在android9.0手機上,當targetSdkVersion<28時 應用的ClassLoader中會有org.jar相關的類,而系統的classloader是沒有的,我們插件使用的是系統classloader,所以在9.0上不管targetSdkVersion爲多少都會報錯,爲防止將來出現類似問題,一般情況下,應用應通過應用classloader加載類,而不是直接訪問系統classloader。這就回到了我在1、中的步驟(3)中描述的問題。在android9.0以下手機上不管target設置爲多少,應用classLoader和系統的classloader中都會有org.jar。目前正在看系統classloader加載的原理,嘗試解決生成自己的classloader

3、驗證結果

targetSdkVersion<28,使用方法:useLibrary 'org.apache.http.legacy’編譯和使用沒有問題
(1)android 8.0手機,target不管爲多少,注意是不管target爲多少, Apache HTTP api都存在,ClassLoader中不管是系統的還是應用的ClassLoader中都存在。
(2)android 9.0(P)手機,target<28,應用ClassLoader中存在,系統ClassLoader中不存在
(3)使用PathClassLoader加載外部org.jar文件,9.0以下手機可以找到org相關,9.0不可以,需要將jar文件轉化成dex形式,target<23時候可以加載成功,但是會出其他錯誤。

4、解決方法

當target>=p時怎麼解決呢,只需要在AndroidManifest.xml的application標籤下和activity同級別,增加如下代碼:

<uses-library android:name="org.apache.http.legacy" android:required="false"/>

但是上述問題,僅能解決應用classloader正常加載Apache相關的類,插件使用系統classloader目前還沒不可以。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章