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目前还没不可以。

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