Android動態部署三:如何從插件apk中啟動Activity(-)


轉載請注明出處:http://blog.csdn.net/ximsfei/article/details/50926406

github地址:https://github.com/ximsfei/DynamicDeploymentApk

在上一篇文章:APK安裝及AndroidManifest.xml解析流程分析中分析了如何通過adb 命令安裝apk,以及源碼中apk解析的流程,根據這部分源碼,我們可以自己去解析AndroidManifest.xml或者直接porting這部分源碼,這樣解析出來的信息會更完整,對后續的開發更有幫助;

接下來我們就來分析一下從Activity中startActivity的流程(Fragment等調用startActivity方法的流程類似,讀者可以對照着這篇文章自己去看),並且我會重點描述一下,在實現動態部署框架時所需要關注的點。

Activity啟動流程

startActivity(new Intent(this, TargetActivity.class));

在Activity中,很簡單的一行代碼,就可以跳轉到TargetActivity了,下圖就是調用這行代碼后的時序圖:
startActivity
其中紅色標注的是在看代碼的過程中需要關注的。

public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
...
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
...
}

首先我們來看,調用startActivity后,會根據不同參數調用startActivityForResult方法,在startActivityForResult方法中又會調用mInstrumentation的execStartActivity方法,在Activity的源碼中我們看到mInstrumentation是在attach方法中初始化的

final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
...
mInstrumentation = instr;
...
}

Instrumentation.java

public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options, UserHandle user) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
...
try {
...
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}

在execStartActivity中會通過binder機制調用到AMS中的startActivity方法,這里的whoThread可以簡單的理解為ActivityThread$ApplicationThread對象,以及intent中攜帶了TargetActivity的信息。

ActivityManagerService.java

@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, options,
UserHandle.getCallingUserId());
}

@Override
public final int startActivityAsUser(...) {
...
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
profilerInfo, null, null, options, false, userId, null, null);
}

ActivityStackSupervisor.java

final int startActivityMayWait(IApplicationThread caller, int callingUid,
String callingPackage, Intent intent, String resolvedType,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
Bundle options, boolean ignoreTargetSecurity, int userId,
IActivityContainer iContainer, TaskRecord inTask) {
...
intent = new Intent(intent);

ActivityInfo aInfo =
resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
...
startActivityLocked(caller, intent, resolvedType, aInfo,
voiceSession, voiceInteractor, resultTo, resultWho,
requestCode, callingPid, callingUid, callingPackage,
realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
componentSpecified, null, container, inTask);
...


final boolean realStartActivityLocked(ActivityRecord r,
ProcessRecord app, boolean andResume, boolean checkConfig)
throws RemoteException {
...
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
...
}

在startActivityMayWait方法中首先會根據intent中攜帶的信息,獲得TargetActivity的信息:ActivityInfo;最終會調用該類中的realStartActivityLocked方法,並在該方法中調用ActivityThread.ApplicationThread中的scheduleLaunchActivity方法。
ActivityThread.ApplicationThread

@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}

ActivityThread

public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
...
handleLaunchActivity(r, null);
} break;
...
}
...
}

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
....
}
...
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
...
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
...
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}

activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
} catch (Exception e) {
....
}
return activity;
}

public Instrumentation getInstrumentation()
{
return mInstrumentation;
}

Instrumentation.java

public Activity newActivity(ClassLoader cl, String className,                                                                                                                                
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}

public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}

從上面的代碼我們可以看到,在進入scheduleLaunchActivity方法后,最終會調用ActivityThread中的performLaunchActivity方法,在該方法中首先會獲取當前apk的ClassLoader對象,調用mInstrumentation的newActivity方法,並傳入ClassLoader方法,在newActivity方法中加載TargetActivity類,實例化。
之后會調用newActivity返回的activity的attach方法,文章開頭我們知道Activity的mIntrumentation成員變量是通過attach方法傳入的,所以Activity中的mIntrumentation就是ActivityThread中的mIntrumentation。在attach方法中還會傳入appContext, app,activityInfo,title等其他信息。之后會為activity設置主題:setTheme,在這些信息設置完后會調用mIntrumentation的callActivityOnCreate方法,之后就會調用activity的performCreate, performStart方法,在這些方法中分別會調用onCreate和onStart方法,再會返回到handleLaunchActivity方法中調用handleResumeActivity方法,也就是activity的onResume方法。

至此TargetActivity已經啟動,startActivity的流程也結束了。

實現動態部署所需要關注的點

首先我們要知道動態部署要怎么做,我們要做的事是通過ClassLoader以及類名調用非安裝的apk中的 Activity, Service, BroadcastReceiver, ContentProvider,從而實現插件化的效果,因為Android的四大組件大部分都需要注冊到AndroidManifest.xml中才能正常使用,所以從前一篇文章:APK安裝及AndroidManifest.xml解析流程分析中,我們已經獲得了插件apk的所有我們想要知道的信息。我們知道了所有Activity,Service等類的類名,以及apk的路徑,當我們開始做的時候,又遇到了新的問題,在TargetActivity啟動的過程中會在ActivityStackSupervisor.java的startActivityMayWait方法中獲取Activity信息,而這些信息必須要注冊到宿主apk的AndroidManifest.xml中才行,插件apk如何在不注冊的情況下還能被查詢到呢,如何啟動呢,如何通過ClassLoader加載類呢。帶着這些疑問我們接着往下看:

DexClassLoader classLoader = new DexClassLoader(apkFile.getAbsolutePath(),
optimizedDexDir, optimizedLibDir, ClassLoader.getSystemClassLoader().getParent());

如何加載比較簡單,我們知道了類名,只要實例化一個屬於插件apk的DexClassLoader就可以了;
如何在不注冊的情況下還能被查詢到呢,不妨想想Activity的標准啟動模式,理論上可以無限實例化,無限啟動,那么我們想到這發現,可以在宿主apk中,注冊一個Stub Activity,這樣每次啟動的是Stub Activity,在通過了startActivityMayWait方法中的查詢之后,將Activity的信息替換成TargetActivity的信息。

那么在startActivity之后如何偽裝成StubActivity呢,在通過檢查之后又如何變回真正的TargetActivity呢?
還有我們需要替換哪些StubActivity的信息呢,在什么時候替換呢?

我們再來看Activity的啟動流程
上面的代碼中我們可以看到原生AOSP的啟動流程中,Activity是在Instrumentation的newActivity方法中實例化的,而且在startActivity后會最先調用Instrumentation的execStartActivity方法,在activity實例化之后會通過調用Instrumentation中的callActivityOnCreate來調用activity的onCreate方法,這個類在啟動TargetActivity的過程中扮演了很重要的角色,而且這些方法都是在startActivityMayWait之后調用的。
在調用startActivity之后,startActivityMayWait之前,會先調用Instrumentation中的execStartActivity方法,在這里我們可以重寫intent中的信息,將TargetActivity偽裝成StubActivity;
從上面startActivity的時序圖中我們可以看到,在performLaunchActivity方法中會先調用Instrumentation的newActivity方法,在該方法中實例化activity,那么我們可以在這里從intent中獲取信息,將StubActivity變回TargetActivity;

那么我們來看一下,成員變量mInstrumentation是在什么時候實例化的呢?

ActivityThread.java

public static ActivityThread currentActivityThread() {                                                                                                                                       
return sCurrentActivityThread;
}

private void attach(boolean system) {
sCurrentActivityThread = this;
...
if (!system) {
...
} else {
try {
mInstrumentation = new Instrumentation();
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}



public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
}

在ActivityThread的main方法中實例化ActivityThread同時會調用attch方法,所以在有ActivityThread對象之后可以說同時就有mInstrumentation了,眾所周知,ActivityThread是每個應用最先初始化的類,是主線程,且只會初始化一次,至此,我們想到了曾今在window開發中用過的鈎子技術,在這我們可以重寫Instrumentation類,在ActivityThread啟動后將mInstrumentation替換成我們重寫的類,這樣,我們就可以很容易的攔截execStartActivity,newActivity等方法,從而干涉Activity的啟動流程了。我們可以通過java的反射機制來完成這項工作,反射機制在這里不做過多解釋。我們可以反射currentActivityThread方法獲取應用的ActivityThread對象,再反射私有成員變量mInstrumentation將其替換成我們重寫的類(DynamicInstrumentation.java)。

Instrumentation instrumentation = DynamicActivityThread.getInstance().getInstrumentation();
if (!(instrumentation instanceof DynamicInstrumentation)) {
DynamicInstrumentation dynamicInstrumentation = new DynamicInstrumentation(instrumentation);
DynamicActivityThread.getInstance().setInstrumentation(dynamicInstrumentation);
}

至此我們已經可以啟動插件apk中的不使用布局文件(可以在Activity中使用java代碼動態創建View)的Activity了。
趕快動手嘗試一下吧!

以下是在performLaunchActivity中調用newActivity方法后的時序圖:
這里寫圖片描述

final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
...
}

在activity實例化后,會先調用attach setTheme方法設置Activity信息,從時序圖以及代碼來看,我猜想:我們需要替換Context,Resources,AssetManager,Theme,Title, Application等信息。
讀者可以嘗試着實現一下,下一篇文章我們會接着講Activity的啟動流程,其中就會講到如何還原TargetActivity的這些信息了。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com