Android M (6.0) generatePackageInfo 偶现错误
并发那么些情况自此会导致 BundleActivator.start()
方法不会被调用
6.0 方法签名
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state, int userId) {
日记中出现万分的调用是
10-25 15:31:35.579 4046-4046/com.wlqq W/System.err: java.lang.NoSuchMethodException: generatePackageInfo [class android.content.pm.PackageParser$Package, class [I, int, long, long, class android.util.ArraySet, class android.content.pm.PackageUserState, int]
6.0.1 的 PackageParser
http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/content/pm/PackageParser.java
5.1.1 的 PackageParser
http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/content/pm/PackageParser.java
Apkplug v3.9 完整日志
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: java.lang.NoSuchMethodException: generatePackageInfo [class android.content.pm.PackageParser$Package, class [I, int, long, long, class android.util.ArraySet, class android.content.pm.PackageUserState, int]
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at java.lang.Class.getMethod(Class.java:624)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at java.lang.Class.getDeclaredMethod(Class.java:586)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.Z.b(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.eg.s(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.eg.t(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.aO(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.ba(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.aK(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.bD.getBaseContext(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.bD.<init>(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.aY(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.bq.getAndroidContext(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at org.tengxin.cl.run(Unknown Source)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at android.os.Handler.handleCallback(Handler.java:739)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at android.os.Looper.loop(Looper.java:148)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5466)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at java.lang.reflect.Method.invoke(Native Method)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
10-27 20:28:11.259 24724-24724/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
10-27 20:28:11.259 24724-24724/com.wlqq E/PackageInfo: ApplicationInfo is null
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: Unable to read ApplicationInfo
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: java.lang.NullPointerException: Attempt to read from field 'android.content.pm.ApplicationInfo android.content.pm.PackageInfo.applicationInfo' on a null object reference
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.eg.t(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.aO(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.ba(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.aK(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.bD.getBaseContext(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.bD.<init>(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.bs.aY(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.bq.getAndroidContext(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at org.tengxin.cl.run(Unknown Source)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at android.os.Handler.handleCallback(Handler.java:739)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at android.os.Looper.loop(Looper.java:148)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5466)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at java.lang.reflect.Method.invoke(Native Method)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
10-27 20:28:11.260 24724-24724/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Apkplug v3.2 完整日志
10-25 15:31:35.579 4046-4046/com.wlqq W/System.err: java.lang.NoSuchMethodException: generatePackageInfo [class android.content.pm.PackageParser$Package, class [I, int, long, long, class android.util.ArraySet, class android.content.pm.PackageUserState, int]
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at java.lang.Class.getMethod(Class.java:624)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at java.lang.Class.getDeclaredMethod(Class.java:586)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at org.tengxin.ag.b(Unknown Source)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at org.tengxin.dx.t(Unknown Source)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at org.tengxin.dx.u(Unknown Source)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at org.tengxin.bf.aQ(Unknown Source)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at org.tengxin.bf.aR(Unknown Source)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at org.tengxin.c.a(Unknown Source)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at org.tengxin.e.execStartActivity(Unknown Source)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at android.app.Activity.startActivityForResult(Activity.java:3930)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at android.app.Activity.startActivityForResult(Activity.java:3890)
10-25 15:31:35.587 4046-4046/com.wlqq W/System.err: at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:820)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at com.wlqq.plugin.wallet.sdk.WalletHelper$1$3.run(WalletHelper.java:255)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at android.os.Handler.handleCallback(Handler.java:739)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at android.os.Looper.loop(Looper.java:148)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5466)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at java.lang.reflect.Method.invoke(Native Method)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
10-25 15:31:35.588 4046-4046/com.wlqq E/PackageInfo: ApplicationInfo is null
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: Unable to read ApplicationInfo
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: java.lang.NullPointerException: Attempt to read from field 'android.content.pm.ApplicationInfo android.content.pm.PackageInfo.applicationInfo' on a null object reference
10-25 15:31:35.588 4046-4046/com.wlqq W/System.err: at org.tengxin.dx.u(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at org.tengxin.bf.aQ(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at org.tengxin.bf.aR(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at org.tengxin.c.a(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at org.tengxin.e.execStartActivity(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.app.Activity.startActivityForResult(Activity.java:3930)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.app.Activity.startActivityForResult(Activity.java:3890)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:820)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at com.wlqq.plugin.wallet.sdk.WalletHelper$1$3.run(WalletHelper.java:255)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.os.Handler.handleCallback(Handler.java:739)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.os.Looper.loop(Looper.java:148)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5466)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at java.lang.reflect.Method.invoke(Native Method)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: java.lang.NullPointerException: Attempt to read from field 'java.lang.String android.content.pm.ApplicationInfo.className' on a null object reference
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at org.tengxin.bf.aR(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at org.tengxin.c.a(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at org.tengxin.e.execStartActivity(Unknown Source)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.app.Activity.startActivityForResult(Activity.java:3930)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.app.Activity.startActivityForResult(Activity.java:3890)
10-25 15:31:35.589 4046-4046/com.wlqq W/System.err: at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:820)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at com.wlqq.plugin.wallet.sdk.WalletHelper$1$3.run(WalletHelper.java:255)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at android.os.Handler.handleCallback(Handler.java:739)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at android.os.Looper.loop(Looper.java:148)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5466)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at java.lang.reflect.Method.invoke(Native Method)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
10-25 15:31:35.590 4046-4046/com.wlqq W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
10-25 15:31:35.590 4046-4046/com.wlqq D/Tracker: track event wallet_launch-error_activity with values {Exception=java.lang.NullPointerException: Attempt to read from field 'java.lang.String android.content.pm.ApplicationInfo.className' on a null object reference}
10-25 15:31:35.590 4046-4094/com.wlqq I/TDLog: onEvent being called! eventId: wallet_launch, eventLabel: error_activity, eventMap: mapSize: 1
插件加运载飞机制
上文
Activity生命周期管理
中大家地完结了『运转没有在AndroidManifest.xml中显式评释的Activity』的职务;通过Hook
AMS
和拦截ActivityThread中H
类对于组件调度大家中标地绕过了AndroidMAnifest.xml的范围。
只是咱们运转的『没有在AndroidManifet.xml中显式评释』的Activity和宿主程序存在于同三个Apk中;通常意况下,插件均以单独的文书存在甚至通过互连网获得,那时候插件中的Activity能还是不可能打响运维呢?
要开动Activity组件肯定先要成立对应的Activity类的对象,从上文
Activity生命周期管理
知道,创立Activity类对象的进度如下:
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
约等于说,系统经过ClassLoader
加载了亟待的Activity类并经过反射调用构造函数创立出了Activity对象。固然Activity组件存在于独立于宿主程序的文本之中,系统的ClassLoader怎么了然去哪里加载呢?由此,假若不做额外的拍卖,插件中的Activity对象竟然都没有艺术成立出来,谈何运维?
因此,要使存在于独立文件或许互连网中的插件被成功运行,首先就需要化解这么些插件类加载的问题。
下文将围绕此题材实行,实现『运行没有在AndroidManifest.xml中显得注解,并且设有于表面插件中的Activity』的职责。
读书本文在此以前,能够先clone一份
understand-plugin-framework,参考此项指标classloader-hook
模块。此外,插件框架原理分析体系文章见索引。
Resources#getIdentifier
至于在插件中利用如下方法
int getIdentifier (String name,
String defType,
String defPackage)
ClassLoader机制
也许有的童鞋还不太精晓Java的ClassLoader机制,小编那里大致介绍一下。
Java虚拟机把描述类的数额从Class文件加载到内部存款和储蓄器,并对数码举办校检、转换解析和伊始化的,最终形成能够被虚拟机直接利用的Java类型,那正是虚拟机的类加运载飞机制。
与那多少个在编写翻译时开始展览链连接工作的言语不一致,在Java语言里面,类型的加载、连接和开端化都以在程序运维期间成功的,那种政策尽管会令类加载时有个别扩充部分性质开支,然则会为Java应用程序提供莫斯中国科学技术大学学的灵活性,Java里天生能够同代拓展的语言特征正是凭借运行期动态加载和动态链接这几个天性完毕的。例如,即使编写1个模样接口的应用程序,能够等到运营时在制订实际的贯彻类;用户能够由此Java与概念的和自定义的类加载器,让三个当地的应用程序能够在运营时从互联网或其余地方加载1个二进制流作为代码的一片段,那种组装应用程序的主意当下一度广泛应用于Java程序之中。从最基础的Applet,JSP到复杂的OSGi技术,都施用了Java语言运维期类加载的表征。
Java的类加载是二个针锋相对复杂的进程;它总结加载、验证、准备、解析和初始化七个级次;对于开发者来说,可控性最强的是加载阶段;加载阶段重点成就三件事:
- 依照2个类的全限定名来获取定义此类的二进制字节流
- 将以此字节流所表示的静态存款和储蓄结构转化为JVM方法区中的运营时数据结构
- 在内部存款和储蓄器中生成3个意味着那一个类的java.lang.Class对象,作为方法区这一个类的各个数据的拜访入口。
『通过2个类的全限定名获取描述此类的二进制字节流』那一个历程被架空出来,便是Java的类加载器模块,也即JDK中ClassLoader
API。
Android
Framework提供了DexClassLoader这几个类,简化了『通过2个类的全限定名获取描述次类的二进制字节流』这些进程;大家只须要报告DexClassLoader多少个dex文件只怕apk文件的门路就能不负众望类的加载。因而本文的内容用一句话就足以归纳:
将插件的dex只怕apk文件报告『合适的』DexClassLoader,借助它完成插件类的加载
至于CLassLoader机制越多的剧情,请参阅『浓密领悟Java虚拟机』那本书。
v3.2
defPackage
无论传什么包名,都只会在插件中搜寻财富
思路分析
Android系统利用了ClassLoader机制来拓展Activity等零件的加载;apk被安装之后,APK文件的代码以及能源会被系统存放在定点的目录(比如/data/app/package_name/base-1.apk
)系统在展开类加载的时候,会自动去这么些恐怕多少个特定的路线来寻找那个类;可是系统并不知道存在于插件中的Activity组件的新闻(插件能够是不管三七二十二地点,甚至是网络,系统相当的小概提前预感),由此平时情状下系统不也许加载大家插件中的类;由此也不曾章程创制Activity的对象,更毫不谈运行组件了。
涸泽而渔那个标题有八个思路,要么完全接管这一个类加载的历程;要么告知系统咱们接纳的插件存在于哪个地方,让系统帮助加载;那三种方法或多或少都亟需干预其一类加载的进度。老规矩,知己知彼,长驱直入。我们第三分析一下,系统是只要做到这些类加载进程的。
我们再一次搬出Activity的创制进度的代码:
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
此间能够很分明地见到,系统经过待运转的Activity的类名className
,然后使用ClassLoader对象cl
把那么些类加载进虚拟机,最终选拔反射成立了那么些Activity类的实例对象。要想干预那几个ClassLoader(告知它大家的门路大概替换他),我们首先得看看那东西到底是个什么来头。(从哪儿创造的)
cl
这一个ClasssLoader对象通过r.packageInfo
目的的getClassLoader()方法赢得,r.packageInfo是3个LoadedApk类的对象;那么,LoadedApk到底是个什么东西??
我们查阅LoadedApk类的文档,唯有一句话,可是说的很明亮:
Local state maintained about a currently loaded .apk.
LoadedApk对象是APK文件在内部存款和储蓄器中的表示。
Apk文件的连带新闻,诸如Apk文件的代码和资源,甚至代码里面包车型客车Activity,Service等零件的音讯大家都得以透过此目标得到。
OK, 我们领略这么些LoadedApk是何方神圣了;接下去大家要搞通晓的是:那几个
r.packageInfo
到底是从哪儿得到的?
作者们本着 performLaunchActivity上溯,辗转handleLaunchActivity回到了 H
类的LAUNCH_ACTIVITY消息,找到了r.packageInfo
的来源:
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
getPackageInfoNoCheck方法很简短,直接调用了getPackageInfo方法:
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
在那么些getPackageInfo方法里面大家发现了头绪:
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
// 获取userid信息
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
// 尝试获取缓存信息
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// 缓存没有命中,直接new
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
// 省略。。更新缓存
return packageInfo;
}
}
以此办法很重点,大家亟须弄驾驭每一步;
先是,它判断了调用方和也许App音讯的一方是否同四个userId;尽管是同二个user,那么能够共享缓存数据(要么缓存的代码数据,要么缓存的财富数量)
接下去尝试获得缓存数据;假设没有打中缓存数据,才通过LoadedApk的构造函数创立了LoadedApk对象;成立成功之后,假诺是同二个uid还放入了缓存。
涉及缓存数据,看过Hook机制之Binder
Hook的童鞋大概就清楚了,大家在此之前成功借助瑟维斯Manager的当地代理使用缓存的机制Hook了各个Binder;由此那里完全可以照猫画虎——大家得到这一份缓存数据,修改里面包车型客车ClassLoader;自个儿支配类加载的进度,这样加载插件中的Activity类的题材就消除了。这就引出了我们加载插件类的率先种方案:
v3.9
defPackage
只有传插件包名或宿主包名,才会在插件中搜索财富
激进方案:Hook掉ClassLoader,自身操刀
从上述分析中我们获悉,在获得LoadedApk的进程中使用了一份缓存数据;那些缓存数据是三个Map
,从包名到LoadedApk的二个炫耀。平常情状下,我们的插件肯定不会存在于那么些目的里面;可是若果大家手动把我们插件的音信添加到里面呢?系统在搜寻缓存的长河中,会直接命中缓存!进而使用大家添加进去的LoadedApk的ClassLoader来加载那个一定的Activity类!那样大家就能接管大家和好插件类的加载进度了!
这么些缓存对象mPackages
留存于ActivityThread类中;老艺术,大家先是获得这一个指标:
// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 获取到 mPackages 这个静态成员变量, 这里缓存了dex包的信息
Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
Map mPackages = (Map) mPackagesField.get(currentActivityThread);
获得那么些Map之后接下去怎么做吧?我们须要填写这么些map,把插件的新闻塞进那么些map里面,以便系统在搜寻的时候能命中缓存。但是这些填充那些Map大家出了索要包名之外,还亟需2个LoadedApk对象;怎样成立一个LoadedApk对象啊?
作者们本来能够一贯反射调用它的构造函数直接开立出须要的目的,可是只要何地有遗漏,构造参数填错了怎么做?又只怕Android的两样版本选用了区别的参数,导致大家创立出来的指标与系统创立出的靶子分歧,不可能work如何做?
之所以大家须要利用与系统完全相同的格局开创LoadedApk对象;从上文分析得知,系统创制LoadedApk对象是由此getPackageInfo
来成功的,由此我们得以调用这些函数来创立LoadedApk对象;可是那些函数是private
的,大家鞭长莫及接纳。
有个别童鞋大概会有问号了,private
不是也能反射到吗?大家真正能够调用那些函数,不过private
标明那几个函数是内部贯彻,可能那一天谷歌载歌载舞,把那个函数改个名字大家就径直GG了;不过public函数区别,public被导出的函数你不大概担保是或不是有人家调用它,由此超越50%状态下不会修改;大家最好调用public函数来保管尽恐怕少的相逢包容性难点。(当然,假诺实际木有路能够考虑调用私有方法,自身处理包容性难点,那么些大家随后也会遇上)
直接调用getPackageInfo
以此私有函数的public函数有同名的getPackageInfo体系和getPackageInfoNoCheck;不难查看源代码发现,getPackageInfo除了得到包的音信,还检查了包的一部分零部件;为了绕过那些验证,我们挑选使用getPackageInfoNoCheck
获取LoadedApk信息。
SDK 版本升级
v3.9 降级到 v3.2 ,启动会 crash
1 java.lang.RuntimeException:Unable to create application com.wuliuqq.client.app.AdminClientApplication: com.tencent.tinker.loader.TinkerRuntimeException: Tinker Exception:onCreate method not found
2 android.app.ActivityThread.handleBindApplication(ActivityThread.java:4480)
3 ......
4 Caused by:
5 java.lang.NoClassDefFoundError:org.tengxin.dI
6 org.tengxin.bQ.a(Unknown Source)
7 org.tengxin.bQ.<init>(Unknown Source)
8 org.tengxin.bQ.<init>(Unknown Source)
9 org.tengxin.bu.start(Unknown Source)
10 org.apkplug.app.FrameworkFactory.start(Unknown Source)
11 org.apkplug.app.FrameworkFactory.start(Unknown Source)
12 org.apkplug.app.FrameworkFactory.start(Unknown Source)
13 com.wlqq.plugin.sdk.PluginManager.init(PluginManager.java:125)
14 com.wuliuqq.client.plugins.PluginManagerInitService.initPluginManagerSync(PluginManagerInitService.java:82)
15 com.wuliuqq.client.app.DiesApplication.initApkPlug(DiesApplication.java:266)
16 com.wuliuqq.client.app.DiesApplication.onCreateMain(DiesApplication.java:249)
17 com.wuliuqq.client.app.DiesApplicationLike.onCreate(DiesApplicationLike.java:78)
18 com.wuliuqq.client.app.DiesApplication.onCreate(DiesApplication.java:190)
19 com.wuliuqq.client.app.DiesApplicationLike.onCreate(DiesApplicationLike.java:73)
20 java.lang.reflect.Method.invokeNative(Native Method)
21 java.lang.reflect.Method.invoke(Method.java:525)
22 com.tencent.tinker.loader.app.TinkerApplication.delegateMethod(TinkerApplication.java:181)
23 com.tencent.tinker.loader.app.TinkerApplication.onCreate(TinkerApplication.java:192)
24 android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1007)
25 android.app.ActivityThread.handleBindApplication(ActivityThread.java:4477)
26 android.app.ActivityThread.access$1300(ActivityThread.java:144)
27 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1319)
28 android.os.Handler.dispatchMessage(Handler.java:99)
29 android.os.Looper.loop(Looper.java:137)
30 android.app.ActivityThread.main(ActivityThread.java:5136)
31 java.lang.reflect.Method.invokeNative(Native Method)
32 java.lang.reflect.Method.invoke(Method.java:525)
33 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
34 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
35 dalvik.system.NativeStart.main(Native Method)
创设插件LoadedApk对象
我们这一步的指标很明显,通过getPackageInfoNoCheck函数创立出大家须要的LoadedApk对象,以供接下来使用。
其一函数的签名如下:
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
所以,为了调用这么些函数,大家需求组织三个参数。其一是ApplicationInfo,其二是CompatibilityInfo;第二个参数顾名思义,代表这些App的包容性音信,比如targetSDK版本等等,那里我们只供给领取出app的新闻,因而平素运用私下认可的包容性即可;在CompatibilityInfo类里面有3个国有字段DEFAULT_COMPATIBILITY_INFO代表默许包容性音讯;由此,我们的重庆大学指标是赢得这一个ApplicationInfo音讯。
Service 使用注意事项
插件中 bind 插件中的 Service
,在代理情势下,不能够应用
Context#getApplication()
获取的 Context
,而急需选拔 Activity
之类的 Context
营造插件ApplicationInfo新闻
小编们第壹看望ApplicationInfo代表什么,这一个类的文书档案说的很明亮:
Information you can retrieve about a particular application. This
corresponds to information collected from the AndroidManifest.xml’s
<application> tag.
也便是说,这么些类正是AndroidManifest.xml里面包车型大巴<application>
那些标签上面包车型大巴新闻;那一个AndroidManifest.xml无疑是三个专业的xml文件,因而我们完全能够团结使用parse来分析这么些新闻。
那么,系统是怎样取得这些新闻的呢?其实Framework就有3个如此的parser,也即PackageParser;理论上,大家也能够借用系统的parser来解析AndroidMAnifest.xml从而获得ApplicationInfo的新闻。但遗憾的是,本条类的兼容性很差;谷歌大约在每六个Android版本都对那个类动刀子,如若坚持不渝使用系统的分析方法,必须写一种类包容行代码!!DroidPlugin就分选了那种格局,相关类如下:
<img
src=”http://7xp3xc.com1.z0.glb.clouddn.com/201601/1459829051777.png”
width=”283″ alt=”DroidPlugin的PackageParser”/>
总的来看那里本人就问你怕不怕!!!那也是大家后边提到的个体可能隐藏的API能够动用,但无法不处理好包容性难点;假若Android
7.0发表,那里估摸得加上八个新的类PackageParseApi24。
笔者那边运用API 23用作示范,本子不一样的恐怕不恐怕运营请自行查阅
DroidPlugin 不一样版本怎么着处理。
OK回到正题,大家决定采用PackageParser类来领取ApplicationInfo音信。下图是API
23上,PackageParser的一些类协会图:
<img
src=”http://7xp3xc.com1.z0.glb.clouddn.com/201601/1459829674687.png”
width=”481″/>
看起来有大家须要的方式generateApplication;确实那样,依靠那些主意大家得以成功地获得ApplicationInfo。
由于PackageParser是@hide的,因而我们必要经过反射实行调用。大家根据那一个generateApplicationInfo方法的签字:
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state)
能够写出调用generateApplicationInfo的反射代码:
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
// 首先拿到我们得终极目标: generateApplicationInfo方法
// API 23 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// public static ApplicationInfo generateApplicationInfo(Package p, int flags,
// PackageUserState state) {
// 其他Android版本不保证也是如此.
Class<?> packageParser$PackageClass = Class.forName("android.content.pm.PackageParser$Package");
Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod("generateApplicationInfo",
packageParser$PackageClass,
int.class,
packageUserStateClass);
要打响调用那个法子,还供给四个参数;由此接下去大家要求一步一步塑造调用此函数的参数消息。
插件中的 PopupWindow 不能够运用动画片
构建PackageParser.Package
generateApplicationInfo方法须要的第五个参数是PackageParser.Package;从名字上看这一个类代表有个别apk包的音信,我们看看文书档案怎么解释:
Representation of a full package parsed from APK files on disk. A
package consists of a single base APK, and zero or more split APKs.
果真,这几个类代表从PackageParser中剖析获得的某些apk包的音信,是磁盘上apk文件在内部存款和储蓄器中的数据结构表示;由此,要取得这一个类,肯定必要分析整个apk文件。PackageParser中解析apk的主干措施是parsePackage,那么些措施再次回到的正是多个Package类型的实例,由此大家调用这些格局即可;使用反射代码如下:
// 首先, 我们得创建出一个Package对象出来供这个方法调用
// 而这个需要得对象可以通过 android.content.pm.PackageParser#parsePackage 这个方法返回得 Package对象得字段获取得到
// 创建出一个PackageParser对象供使用
Object packageParser = packageParserClass.newInstance();
// 调用 PackageParser.parsePackage 解析apk的信息
Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
// 实际上是一个 android.content.pm.PackageParser.Package 对象
Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0);
那样,大家就收获了generateApplicationInfo的率先个参数;第四个参数是解析包使用的flag,大家直接选用解析全体音讯,也正是0;
插件 Application#onCreate 大概会产出没有调用的事态
- apkplug 版本 v3.2.0
线上总结数据差不多 20%
存在那种气象,由此建议不要把重要的初阶化逻辑放在个中
构建PackageUserState
其八个参数是PackageUserState,代表差别用户中包的音信。由于Android是一个多职务多用户系统,因而分裂的用户同二个包恐怕有两样的情事;那里大家只要求得到包的新闻,由此平昔采取暗许的即可;
时至明日,generateApplicaionInfo的参数大家早就整整结构实现,直接调用此方法即可得到大家必要的applicationInfo对象;在回来从前大家须要做一点细微修改:使用系统系统的这一个点子分析获得的ApplicationInfo对象中并从未apk文件自己的音信,所以我们把分析的apk文件的路子设置一下(ClassLoader依赖dex文件以及apk的不二法门):
// 第三个参数 mDefaultPackageUserState 我们直接使用默认构造函数构造一个出来即可
Object defaultPackageUserState = packageUserStateClass.newInstance();
// 万事具备!!!!!!!!!!!!!!
ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser,
packageObj, 0, defaultPackageUserState);
String apkPath = apkFile.getPath();
applicationInfo.sourceDir = apkPath;
applicationInfo.publicSourceDir = apkPath;
乐蛙 ROM Android 4.1 在后台线程中初始化 Universal Image Loader 会出现卓殊
06-13 11:28:13.676 5460-5863/com.wlqq W/System.err: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at android.os.Handler.<init>(Handler.java:121)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at android.app.Application.<init>(Application.java:81)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at java.lang.Class.newInstanceImpl(Native Method)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at java.lang.Class.newInstance(Class.java:1319)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at android.app.Instrumentation.newApplication(Instrumentation.java:982)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at android.app.Instrumentation.newApplication(Instrumentation.java:967)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at org.tengxin.bf.aR(Unknown Source)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at org.tengxin.bp.getApplicationContext(Unknown Source)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at com.nostra13.universalimageloader.core.ImageLoaderConfiguration$Builder.<init>(ImageLoaderConfiguration.java:191)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at com.wlqq.plugin.wallet.PluginActivator.initImageLoader(PluginActivator.java:60)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at com.wlqq.plugin.wallet.PluginActivator.init(PluginActivator.java:55)
06-13 11:28:13.686 5460-5863/com.wlqq W/System.err: at com.wlqq.plugin.wallet.PluginActivator.start(PluginActivator.java:31)
替换ClassLoader
插件与宿主间通信是还是不是能够动用 ResultReceiver 做为通用的 Callback 机制?
不可以。调用 intent.getParcelableExtra
获取 ResultReceiver
会出现以下很是
06-01 10:17:15.270 19102-19102/com.hcb.driver W/TDLog: UncaughtException in Thread main
java.lang.RuntimeException: Error receiving broadcast Intent { act=install_plugin flg=0x10 pkg=com.hcb.driver (has extras) } in com.wlqq.plugin.sdk.PluginManager$InstallerService@448a9a9
at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:893)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5432)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:735)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625)
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: plugin.store.WalletServiceManager$1
at android.os.Parcel.readParcelableCreator(Parcel.java:2432)
at android.os.Parcel.readParcelable(Parcel.java:2358)
at android.os.Parcel.readValue(Parcel.java:2264)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2614)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.Bundle.getParcelable(Bundle.java:786)
at android.content.Intent.getParcelableExtra(Intent.java:5381)
at com.wlqq.plugin.sdk.PluginManager$InstallerService.onReceive(PluginManager.java:1204)
获取LoadedApk信息
刚刚为了赢得ApplicationInfo大家费了好大学一年级番生气;回想一下大家的初衷:
大家最终的指标是调用getPackageInfoNoCheck得到LoadedApk的音信,并替换其中的mClassLoader然后把把添加到ActivityThread的mPackages缓存中;从而达到大家应用自个儿的ClassLoader加载插件中的类的目标。
今昔大家早已获得了getPackageInfoNoCheck那些格局中关键的率先个参数applicationInfo;上文提到第②个参数CompatibilityInfo代表设备包容性信息,直接使用暗中认可的值即可;因而,五个参数都早就组织出来,我们得以调用getPackageInfoNoCheck获取LoadedApk:
// android.content.res.CompatibilityInfo
Class<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);
Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
defaultCompatibilityInfoField.setAccessible(true);
Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);
ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);
Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);
大家中标地组织出了LoadedAPK,
接下来我们供给替换当中的ClassLoader,然后把它添加进ActivityThread的mPackages中:
String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath();
String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath();
ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader());
Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
mClassLoaderField.set(loadedApk, classLoader);
// 由于是弱引用, 因此我们必须在某个地方存一份, 不然容易被GC; 那么就前功尽弃了.
sLoadedApk.put(applicationInfo.packageName, loadedApk);
WeakReference weakReference = new WeakReference(loadedApk);
mPackages.put(applicationInfo.packageName, weakReference);
大家的那些CustomClassLoader格外不难,直接接轨了DexClassLoader,什么都并未做;当然那里能够直接行使DexClassLoader,那里再度创造三个类是为着更有区分度;未来也得以因而修改这一个类实现对于类加载的支配:
public class CustomClassLoader extends DexClassLoader {
public CustomClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, libraryPath, parent);
}
}
到那边,大家早已成功地把把插件的音讯放入ActivityThread中,那样大家插件中的类能够得逞地被加载;因而插件中的Activity实例能被成功第成立;由于整个流程比较复杂,我们大致梳理一下:
- 在ActivityThread接收到IApplication的
scheduleLaunchActivity远程调用之后,将音讯转发给H
H
类在handleMessage的时候,调用了getPackageInfoNoCheck方法来取得待运维的机件新闻。在那个法子中会优先查找mPackages
中的缓存新闻,而大家已经手动把插件音信添加进去;由此能够成功命中缓存,获取到独门存在的插件音讯。H
类然后调用handleLaunchActivity最后转化到performLaunchActivity方法;这么些主意应用从getPackageInfoNoCheck中得到LoadedApk中的mClassLoader来加载Activity类,进而使用反射制造Activity实例;接着成立Application,Context等成就Activity组件的运行。
看起来好像早就天衣无缝万事大吉了;可是运营一下会现出三个老大,如下:
04-05 02:49:53.742 11759-11759/com.weishu.upf.hook_classloader E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.weishu.upf.hook_classloader, PID: 11759
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.weishu.upf.ams_pms_hook.app/com.weishu.upf.ams_pms_hook.app.MainActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.weishu.upf.ams_pms_hook.app; is package not installed?
不当提醒说是不能够实例化
Application
,而Application的创建也是在performLaunchActivity中开始展览的,那里有个别诡异,我们精心翻看一下。
插件与宿主间通信是或不是足以应用 Local 布罗兹cast ?
不得以。但能够运用普通的 Broadcast
绕过系统一检查查
透过ActivityThread的performLaunchActivity方法能够识破,Application通过LoadedApk的makeApplication方法成立,大家查阅这一个主意,在源码中发现了上文卓殊抛出的地点:
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
木有办法,大家唯有一行一行地翻看究竟是哪儿抛出那一个丰富的了;所幸代码不多。(所以说,缩短相当范围是一件多么主要的工作!!!)
先是句 getClassLoader()
没什么困惑的,固然措施很短,但是它木有抛出任何尤其(当然,它调用的代码只怕抛出特别,万一找不到只可以尤其深搜了;所以作者觉着这里应该使用受检非凡)。
接下来我们看第贰句,假诺包名不是android
发端,那么调用了二个称为initializeJavaContextClassLoader的方法;大家查阅这么些法子:
private void initializeJavaContextClassLoader() {
IPackageManager pm = ActivityThread.getPackageManager();
android.content.pm.PackageInfo pi;
try {
pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());
} catch (RemoteException e) {
throw new IllegalStateException("Unable to get package info for "
+ mPackageName + "; is system dying?", e);
}
if (pi == null) {
throw new IllegalStateException("Unable to get package info for "
+ mPackageName + "; is package not installed?");
}
boolean sharedUserIdSet = (pi.sharedUserId != null);
boolean processNameNotDefault =
(pi.applicationInfo != null &&
!mPackageName.equals(pi.applicationInfo.processName));
boolean sharable = (sharedUserIdSet || processNameNotDefault);
ClassLoader contextClassLoader =
(sharable)
? new WarningContextClassLoader()
: mClassLoader;
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
那边,我们找出了这些尤其的发源:原来此地调用了getPackageInfo
措施赢得包的新闻;而我们的插件并没有安装在系统上,由此系统肯定觉得插件没有安装,这么些艺术自然回来null。所以,大家还要欺骗一下PMS,让系统觉得插件已经设置在系统上了;至于如何欺骗
PMS,Hook机制之AMS&PMS
有详细分解,那里直接付出代码,不赘述了:
private static void hookPackageManager() throws Exception {
// 这一步是因为 initializeJavaContextClassLoader 这个方法内部无意中检查了这个包是否在系统安装
// 如果没有安装, 直接抛出异常, 这里需要临时Hook掉 PMS, 绕过这个检查.
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 获取ActivityThread里面原始的 sPackageManager
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(currentActivityThread);
// 准备好代理对象, 用来替换原始的对象
Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
new Class<?>[] { iPackageManagerInterface },
new IPackageManagerHookHandler(sPackageManager));
// 1. 替换掉ActivityThread里面的 sPackageManager 字段
sPackageManagerField.set(currentActivityThread, proxy);
}
OK到此地,大家早已能够得逞地加载简单的单独的存在于表面文件系统中的apk了。至此
关于 DroidPlugin
对于Activity生命周期的田管已经完全讲解甘休了;那是一种极其错综复杂的Activity管理方案,我们唯有写2个用来驾驭的demo就Hook了一定多的东西,在Framework层来回牵扯;那在那之中的始末要统统把握清楚还请读者亲自翻阅源码。其它,小编在此
对DroidPlugin 作者献上笔者的膝盖~那里面包车型客车神秘让人登峰造极!
上文给出的方案中,我们完全接管了插件中类的加载进度,那是一种对峙暴力的消除方案;能或不能够更和蔼一点吧?通俗来说,我们得以挑选改正,而不是革命——告诉系统ClassLoader一些不可或缺音讯,让它援助完毕插件类的加载。
是还是不是能够动用 overridePendingTransition 设置 Activity 切换动画?
无法使用, 使用该措施大概会造成 Activity 界面卡死
封建方案:委托系统,让系统接济加载
咱俩再一次搬出ActivityThread中加载Activity类的代码:
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
我们明白这一个r.packageInfo中的r
是经过getPackageInfoNoCheck获取到的;在『激进方案』中我们把插件apk手动添加进缓存,采纳本人加载办法消除;要是我们不干预这几个进程,导致不可能命中mPackages中的缓存,会生出什么?
查阅 getPackageInfo方法如下:
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
// 略
}
}
能够观看,没有打中缓存的情景下,系统直接new了1个LoadedApk;注意那么些构造函数的首个参数aInfo
,那是2个ApplicationInfo类型的靶子。在『激进方案』中大家为了取得独立插件的ApplicationInfo花了很多心情;那么只要不做别的处理那里流传的那个aInfo
参数是什么?
追本溯源简单发现,那些aInfo是从我们的就义品StubActivity中得到的!而StubActivity存在于宿主程序中,所以,那些aInfo
对象表示的其实正是宿主程序的Application音讯!
咱俩知晓,接下去会采用new出来的这一个LoadedApk的getClassLoader()方法拿到到ClassLoader来对插件的类进行加载;而收获到的那几个ClassLoader是宿主程序选择的ClassLoader,因而以后还不可能加载插件的类;那么,大家能否让宿主的ClasLoader获得加载插件类的力量吗?;假如大家告知宿主使用的ClassLoader插件使用的类在哪儿,就能帮助她做到加载!
怎么安顿 proguard 混淆?
宿主
-dontwarn org.apkplug.app.**
-dontwarn org.apkplug.Bundle.**
-dontwarn org.osgi.framework.**
-dontwarn org.osgi.service.**
-keep class org.apkplug.app.** { *; }
-keep class org.apkplug.Bundle.** { *; }
-keep class org.osgi.framework.** { *; }
-keep class org.osgi.service.** { *; }
-keep class org.tengxin**
-keep class org.apkplug.mxdstream** {
<fields>;
<methods>;
}
-keepclassmembers class * extends android.content.ContentResolver {
<methods>;
}
-keepclassmembers class * extends android.app.INotificationManager {
<fields>;
<methods>;
}
-keep class android.app.ITransientNotification {
<fields>;
<methods>;
}
-keep class android.service.notification** {
<fields>;
<methods>;
}
-keep class android.app.INotificationManager {
<fields>;
<methods>;
}
-keep class android.content.pm**
-keep class android.os**
-keepclassmembers class * extends android.content.pm.PackageManager {
<methods>;
}
-keep class android.content.res.MiuiResources
-keep class android.app.ApplicationPackageManager
-keepclassmembers class * extends android.app.ApplicationPackageManager {
<methods>;
}
-dontwarn org.tengxin.sv.**
-dontwarn com.msg.**
-dontwarn com.apkplug.CloudService.**
-dontwarn com.apkplug.base.**
-dontwarn com.apkplug.Ads**
-dontwarn com.apkplug.AdsPlug**
-dontwarn com.apkplug.Analytics**
-dontwarn com.apkplug.Feedback**
-dontwarn com.apkplug.Analytics.Bean**
-dontwarn org.openudid**
-keep class org.tengxin.sv** { *; }
-keep class org.osgi** { *; }
-keep class com.msg**{ *; }
-keep class com.apkplug.CloudService**{ *; }
-keep class com.apkplug.base** { *; }
-keep class com.apkplug.Ads** { *; }
-keep class com.apkplug.AdsPlug** { *; }
-keep class com.apkplug.Analytics** { *; }
-keep class com.apkplug.Feedback** { *; }
-keep class com.apkplug.Analytics.Bean** { *; }
-keep class org.openudid** { *; }
插件
-libraryjars android-support-v4.jar
-libraryjars osgi.jar
-keep class * implements org.osgi.** { *; }
-keep class * implements org.apkplug.Bundle.** { *; }
-keep class org.apkplug.mxdstream** {
<fields>;
<methods>;
}
宿主的ClassLoader在何地,是绝无仅有的吗?
地点说到,大家能够通过报告宿主程序的ClassLoader插件使用的类,让宿主的ClasLoader实现对于插件类的加载;那么难题来了,我们什么赢获得宿主的ClassLoader?宿主程序行使的ClasLoader私下认可景况下是全局唯一的呢?
答案是迟早的。
因为在FrameWork中宿主程序也是应用LoadedApk代表的,仿佛Activity运营是加载Activity类一样,宿主中的类也都是经过LoadedApk的getClassLoader()方法获得的ClassLoader加载的;由类加运载飞机制的『双亲委派』脾性,只要有2个应用程序类由某一个ClassLoader加载,那么它引用到的其余类除非父加载器能加载,不然都以由那同二个加载器加载的(不根据双亲委派模型的除外)。
意味着宿主的LoadedApk在Application类中有2个分子变量mLoadedApk
,而那几个变量是从ContextImpl中赢得的;ContextImpl重写了getClassLoader方法,因而我们在Context环境中一直getClassLoader()获取到的就是宿主程序唯一的ClassLoader。
宿主怎样调用插件 Activity 并赢得结果?
调用 startActivityForResult
方法,不要选用
Intent.FLAG_ACTIVITY_NEW_TASK
LoadedApk的ClassLoader到底是何等?
明天大家有限支撑了『使用宿主ClassLoader扶助加载插件类』可行性;那么大家应有怎样形成这一个历程吧?
一目掌握,百战百胜。
不管是宿主程序依然插件程序都以通过LoadedApk的getClassLoader()方法再次来到的ClassLoader实行类加载的,重临的那几个ClassLoader到底是个如胡力夫西??那个点子源码如下:
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader != null) {
return mClassLoader;
}
if (mIncludeCode && !mPackageName.equals("android")) {
// 略...
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,
mBaseClassLoader);
StrictMode.setThreadPolicy(oldPolicy);
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
} else {
mClassLoader = mBaseClassLoader;
}
}
return mClassLoader;
}
}
能够看来,非android
初叶的包和android
始发的包分别使用了三种差别的ClassLoader,大家只关怀第二种;由此继续跟踪ApplicationLoaders类:
public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
{
ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
synchronized (mLoaders) {
if (parent == null) {
parent = baseParent;
}
if (parent == baseParent) {
ClassLoader loader = mLoaders.get(zip);
if (loader != null) {
return loader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader =
new PathClassLoader(zip, libPath, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
mLoaders.put(zip, pathClassloader);
return pathClassloader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return pathClassloader;
}
}
能够观看,应用程序使用的ClassLoader都以PathClassLoader类的实例。那么,那一个PathClassLoader是何等呢?从Android
SDK给出的源码只可以看到那样多:
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
SDK没有导出那个类的源码,大家去androidxref上边看;发现实际这一个类真的就好像此多内容;大家一连查看它的父类BaseDexClassLoader;ClassLoader嘛,我们查阅findClass或然defineClass方法,BaseDexClassLoader的findClass方法如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
可以看出,查找Class的任务通过pathList
完成;这个pathList
是一个DexPathList
类的靶子,它的findClass
艺术如下:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
本条DexPathList内部有3个名叫dexElements的数组,然后findClass的时候会遍历那些数组来查找Class;倘使我们把插件的音讯塞进这几个数组里面,那么不就可见成功类的加载进程吧?!!
插件中怎么着集成微信支付?
宿主
- 需要 release 签名
- 在
AndroidManifest.xml
中声明
${宿主应用的包名}.wxapi.WXPayEntryActivity
,设置
android:exported="true"
插件
- 创建类
${宿主应用的包名}.wxapi.WXPayEntryActivity
,在在那之中处理微信支付回调 - 在
plugin.xml
中配置导出该Activity
参考
给默认ClassLoader打补丁
因而上述分析,我们驾驭,能够把插件的连带新闻放入BaseDexClassLoader的表示dex文件的数组里面,那样宿主程序的ClassLoader在举行类加载,遍历那几个数组的时候,会活动遍历到大家添加进去的插件消息,从而实现插件类的加载!
接下去,大家实现那个进度;大家会用到某个较为复杂的反射技术哦~可是代码非常的短:
public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 获取 BaseDexClassLoader : pathList
Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathListObj = pathListField.get(cl);
// 获取 PathList: Element[] dexElements
Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");
dexElementArray.setAccessible(true);
Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);
// Element 类型
Class<?> elementClass = dexElements.getClass().getComponentType();
// 创建一个数组, 用来替换原始的数组
Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
// 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));
Object[] toAddElementArray = new Object[] { o };
// 把原始的elements复制进去
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
// 插件的那个element复制进去
System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
// 替换
dexElementArray.set(pathListObj, newElements);
}
短短的二十几行代码,大家就完事了『委托宿主ClassLoader加载插件类』的天职;因而第两种方案也表露实现!我们简要总括一下这种方法的规律:
- 暗中认可情形下performLacunchActivity会动用替身StubActivity的ApplicationInfo也等于宿主程序的CLassLoader加载全体的类;大家的思路是告诉宿主ClassLoader大家在哪,让其帮助成功类加载的经过。
- 宿主程序的ClassLoader最后继承自BaseDexClassLoader,BaseDexClassLoader通过DexPathList进行类的追寻进程;而以此查找通过遍历2个dexElements的数组完毕;咱俩由此把插件dex添加进这么些数组就让宿主ClasLoader获取了加载插件类的能力。
Apkplug 参考文档
小结
本文中大家选拔三种方案成功完成了『运行没有在AndroidManifest.xml中体现评释,并且存在于表面插件中的Activity』的职务。
『激进方案』中大家自定义了插件的ClassLoader,并且绕开了Framework的检查和测试;利用ActivityThread对于LoadedApk的缓存机制,大家把带领这么些自定义的ClassLoader的插件新闻添加进mPackages
中,进而成功了类的加载进程。
『保守方案』中我们深远探索了系统选拔ClassLoader
findClass的历程,发现应用程序使用的非系统类都以因而同一个PathClassLoader加载的;而以此类的尾声父类BaseDexClassLoader通过DexPathList完毕类的检索过程;大家hack了那些查找进程,从而成就了插件类的加载。
那三种方案孰优孰劣呢?
很明白,『激进方案』比较麻烦,从代码量和分析进度就能够看出来,那种体制至极复杂;而且在解析apk的时候我们使用的PackageParser的包容天性外差,大家只好手动处理每二个本子的apk解析api;其它,它Hook的地方也有点多:不仅需求Hook
AMS和H
,还需要Hook ActivityThread的mPackages
和PackageManager!
『保守方案』则不难得多(纵然原理也不简单),不仅代码很少,而且Hook的地方也不多;有少数清淤的情致,从最最上层Hook住了全副类的加载进度。
可是,我们无法大致地说『保守方案』比『激进方案』好。从根本上说,那三种方案的异样在哪呢?
『激进方案』是多ClassLoader构架,每二个插件都有二个融洽的ClassLoader,由此类的隔开性极度好——如若不一致的插件使用了同二个库的不比版本,它们善罢甘休!『保守方案』是单ClassLoader方案,插件和宿主程序的类全体都经过宿主的ClasLoader加载,纵然代码不难,不过鲁棒性很差;一旦插件之间依然插件与宿主之间利用的类库有争执,那么直接GG。
多ClassLoader还有一个优点:能够真正到位代码的热加载!借使插件供给提高,直接重新成立一个自定的ClassLoader加载新的插件,然后替换掉原来的本子即可(Java中,分歧ClassLoader加载的同三个类被认为是见仁见智的类);单ClassLoader的话落成充裕辛劳,有恐怕要求重启进度。
在J2EE领域四川中国广播公司大采用ClasLoader的地点均运用多ClassLoader架构,比如Tomcat服务器,Java模块化事实标准的OSGi技术;所以,大家有丰硕的说辞认为选料多ClassLoader架构在大部景观下是明智之举。
当前开源的插件方案中,DroidPlugin选用的『激进方案』,Small选择的『保守方案』那么,有没有两种优点兼顾的方案吧??
答案自然是有的。
DroidPlugin和Small的共同点是两者都是非侵入式的插件框架;什么是『非侵入式』呢?打个比方,你运营3个插件Activity,直接行使startActivity
即可,就跟开发普通的apk一样,开发插件和普通的程序对于开发者来说没有怎么差异。
要是我们终将水平上废弃那种『侵入性』,那么大家就能兑现四个双边优点兼而有之的插件框架!那里小编先卖个典型~
OK,本文的内容就到此地了;关于『插件机制对于Activity的处理格局』也就此截止。要表达的是,在本文的『保守方案』其实只处理了代码的加载进程,它并不可能加载有能源的apk!所以近日自个儿那些达成大旨没什么暖用;当然作者那边只是就『代码加载』进行举例;至于财富,那牵扯到别的二个难点——插件系统的能源管理机制本条在一而再小说的格外时机作者会单独讲解。