Android动态部署三:如何从插件apk中启动Activity(-)
Posted ximsfei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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了,下图就是调用这行代码后的时序图:
其中红色标注的是在看代码的过程中需要关注的。
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的这些信息了。
以上是关于Android动态部署三:如何从插件apk中启动Activity(-)的主要内容,如果未能解决你的问题,请参考以下文章
Android动态部署四:如何从插件apk中启动Activity
Android动态部署五:如何从插件apk中启动Service
Android动态部署六:如何从插件apk中启动BroadcastReceiver和ContentProvider
Android动态部署六:如何从插件apk中启动BroadcastReceiver和ContentProvider
Android动态部署五:怎样从插件apk中启动Service
Android 插件化VirtualApp 安装并启动资源中自带的 APK 插件 ( 添加依赖库 | 准备插件 APK | 启动插件引擎 | 拷贝 APK 插件 | 安装插件 | 启动插件 )(代码片