插件化簡(jiǎn)介
Android中熱修復(fù)主要用來(lái)修復(fù)bug,插件化則主要用來(lái)增加功能,將一些獨(dú)立的功能打包為單獨(dú)的dex作為插件。在需要的時(shí)候再動(dòng)態(tài)加載。
Android中的插件化以Hook方式為主流。
Activity插件化
Activity的插件化必然涉及Activity的啟動(dòng)過(guò)程。從Android中根Activity的啟動(dòng)過(guò)程可知,Activity的啟動(dòng)分為3個(gè)階段
Launcher或Activity請(qǐng)求AMS過(guò)程(第一次啟動(dòng)應(yīng)用程序或應(yīng)用程序打開(kāi)新的Activity)
AMS到ApplicationThread的調(diào)用過(guò)程
ActivityThread啟動(dòng)Activity過(guò)程
因?yàn)锳ctivity啟動(dòng)時(shí)必須先在Manifest中注冊(cè)。而插件中的Activity是無(wú)法提前注冊(cè)的。再根據(jù)Activity的啟動(dòng)過(guò)程,那么Activity的插件化可以分為以下3步:
占坑:使用一個(gè)占坑Activity-LocalActivity在Manifest中注冊(cè),避免插件Activity沒(méi)有注冊(cè)導(dǎo)致崩潰的情況
繞過(guò)AMS驗(yàn)證:?jiǎn)?dòng)插件Activity-PlugActivity時(shí),將PlugActivity替換為L(zhǎng)ocalActivity,進(jìn)行AMS的驗(yàn)證,生命周期及棧管理。
還原Activity:AMS調(diào)用ActivityThread啟動(dòng)Activity時(shí),將LocalActivity再替換為PlugActivity
那么再看每一步的具體實(shí)現(xiàn):
-
創(chuàng)建占坑Activity-LocalActivity
//一個(gè)空實(shí)現(xiàn)的Activity public class LocalActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_local); } } //在manifest中注冊(cè) <activity android:name=".LocalActivity"></activity> -
繞過(guò)AMS驗(yàn)證:
要繞過(guò)AMS驗(yàn)證,必須讓在Manifest中注冊(cè)的LocalActivity代替要啟動(dòng)的PlugActivity去驗(yàn)證,那么就要在啟動(dòng)時(shí)將PlugActivity替換為L(zhǎng)ocalActivity。
在Android中Hook簡(jiǎn)析中分析過(guò),Activity啟動(dòng)時(shí)調(diào)用了Instrumentation的execStartActivity(),那么可以通過(guò)Hook的方式在這里進(jìn)行替換,先看一下之前Hook的代碼:
public class InstrumentationProxy extends Instrumentation { Instrumentation instrumentation; private Method method; //持有真正啟動(dòng)Activity的Instrumentation引用 public InstrumentationProxy(Instrumentation instrumentation) { this.instrumentation = instrumentation; } //重寫(xiě)啟動(dòng)Activity的execStartActivity() public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { //將要啟動(dòng)的Activity改為L(zhǎng)ocalActivity intent.setClassName(who,"com.madnessxiong.client.LocalActivity"); //通過(guò)反射獲取Instrumentation的class對(duì)象 Class<? extends Instrumentation> aClass = Instrumentation.class; try{ //通過(guò)class對(duì)象獲取真正啟動(dòng)Activity的execStartActivities() method = aClass.getMethod("execStartActivity", Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class); //通過(guò)反射的方式執(zhí)行真正啟動(dòng)Activity的execStartActivities() ActivityResult invoke = (ActivityResult) method.invoke(instrumentation, who, contextThread, token, target, intent, options); return invoke; }catch (Exception e){ } return null; } }在這里修改了intent,將要啟動(dòng)的Activity改為了LocalActivity。完成了替換工作。這時(shí)啟動(dòng)Activity的就是LocalActivity。由于LocalActivity在Manifest中注冊(cè)過(guò),所以可以通過(guò)AMS的校驗(yàn)。
-
還原Activity
AMS最終會(huì)調(diào)用到ActivityThread.performLaunchActivity():
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { //在newActivity()中用類(lèi)加載器創(chuàng)建activity的實(shí)例, activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); return activity; }然后通過(guò)mInstrumentation.newActivity()創(chuàng)建一個(gè)Activity,那么這里可以作為一個(gè)hook點(diǎn)。代理Instrumentation的newActivity()。
根據(jù)上面的分析,那么通過(guò)Hook的方式將ActivityThread的mInstrumentation替換為InstrumentationProxy:
public void HookActivityThread(){ try { //獲取ContextImpl.class Class<?> contextImplClass = Class.forName("android.app.ContextImpl"); //獲取mMainThread對(duì)應(yīng)的成員變量 Field mMainThreadFiled = contextImplClass.getDeclaredField("mMainThread"); mMainThreadFiled.setAccessible(true); //成員變量activityThread Object activityThread = mMainThreadFiled.get(getBaseContext()); //獲取ActivityThread.class Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); //獲取Instrumentation屬性 Field field = activityThreadClass.getDeclaredField("mInstrumentation"); field.setAccessible(true); Instrumentation mInstrumentation = (Instrumentation)field.get(activityThread); InstrumentationProxy instrumentationProxy = new InstrumentationProxy(mInstrumentation); field.set(activityThread,instrumentationProxy); }catch (Exception e){ } }由于A(yíng)ctivityThread時(shí)@hide無(wú)法直接獲取它的對(duì)象,而在進(jìn)程啟動(dòng)時(shí)已經(jīng)將一個(gè)mMainThread存入了ContextImpl中。所以這里先通過(guò)反射的方式在ContextImpl中獲取了ActivityThread對(duì)象,最后用InstrumentationProxy替換mInstrumentation。那么調(diào)用newActivity()時(shí),就會(huì)來(lái)到InstrumentationProxy.newActivity():
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newActivity(cl,"com.madnessxiong.client.PlugActivity",intent); }在InstrumentationProxy.newActivity()中,直接調(diào)用父類(lèi),也就是Instrumentation自身的newActivity(),將類(lèi)名改為實(shí)際要啟動(dòng)的PlugActivity。就完成了Activity的還原。而整個(gè)替換的過(guò)程只是對(duì)intent中的類(lèi)名進(jìn)行了替換,而在A(yíng)MS中是通過(guò)token對(duì)Activity進(jìn)行驗(yàn)證的,所以并不影響它的生命周期。