其基本原理,就是hook系统的activity service等关键组件,当需要启动插件内的一些组件式,走自定义逻辑,当启动本地apk中的组件时,走原生逻辑。
一、系统相关。
在分析滴滴插件化框架之前,需要弄清楚android系统中应用的启动流程,以及和系统的通信调度相关。
有几个关键类需要了解下:ActivityThread.java ApplicationThread.java ActivityManagerService.java Instrumentation.java
启动流程如下:
在laucher里面启动一个新的app时,流程参考老罗文章:
整个应用程序的启动过程要执行很多步骤,但是整体来看,主要分为以下五个阶段:
一. Step1 -Step 11:Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity;
二. Step 12 -Step 16:ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态;
三. Step 17 -Step 24:Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行;
四. Step 25 -Step 27:ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信;
五. Step 28 -Step 35:ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切备就绪,它可以真正执行Activity的启动
ActivityManagerService:
这个是系统核心类,几乎掌管了整个系统启动相关的事宜。
ActivityThread:
它是一个应用进程的主线程,有一个死循环来分发进程需要处理的工作。
ApplicationThread:
用途参照启动流程中的step4.
Instrumentation:
里面的方法如下截图,基本上管理了所有与Activity的相关交互工作。So,这个类需要被hook
二、源码分析。
如果想从主apk里面启动插件apk相关的activity或service,那么首先必须先把插件中的相关activity和service加载进来,整个加载流程如下。
针对hook的这几个系统类,Instrumentation使用了一个子类对象(VAInstrumentation)来代替原本的instrumentation,这个子类里面做的事情很简单。复写了父类的newActivity()(就是当我们创建activity时调用的方法),利用try catch捕捉classnotfoundexception,正常情况下说明我们想加载的是插件apk中的activity,但是默认情况,我们的应用是没有把插件apk中的类加载进来的,所以报找不到类的错误。So,这个时候,我们就可以去插件apk中加载对应的activity了。
当需要启动一个activity时,一般使用startActivity(),那么就跟随这个方法,看看其调用链。
public void startActivityForResult(Intentintent, int requestCode, @Nullable Bundle options) {
if(mParent == null) {
//在这里看到调用了instrumentation的execStartActivity(),那么我们就需要针对instrumentation的这个方法进行处理,可以看到在virtualapk代码中,确实是这样处理的。
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this,mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
…………
}
Instrumentation.java
publicActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if(referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
…………
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
//实际调用的是ActivityManagerNative,其实就是系统调用ActivityManagerService来启动activity.
//这个就不深究了。既然启动activity需要调用Instrumentation的这个方法,so 我们需要处理这个方法。
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;
}
Instrumentation.java
//复写的父类中的方法.
publicActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
//null component is an implicitly intent
if(intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]",intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
// resolve intent with Stub Activity if needed
//启动一个本地预置的activity,不过在其intent中加入了 plugin标志位,以及目标包名,目标类名
//接下来继续走正常的activity启动流程,直到newActivity()
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
ActivityResult result = realExecStartActivity(who, contextThread, token,target,
intent, requestCode, options);
return result;
}
整个调用流程如下图片:
@Override
publicActivity newActivity(ClassLoader cl, String className, Intent intent) throwsInstantiationException, IllegalAccessException, ClassNotFoundException {
try {
//先从主apk的classloader中加载类,注意此时的类名是<activityandroid:name=".B$1" android:launchMode="singleTop"/>,又因为本地是没有创建这个类的,所以肯定会报找不到类的错误 cl.loadClass(className);
}catch (ClassNotFoundException e) {
//可以看下LoadedPlugin的实现,它是怎样把插件apk中的类加载进来的。
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
//从加载的插件资源中,找到对应的activity
String targetClassName = PluginUtil.getTargetActivity(intent);
Log.i(TAG, String.format("newActivity[%s : %s]", className,targetClassName));
if (targetClassName != null) {
//plugin.getClassLoader() 已经是加载过插件资源的classloader了,这里就创建了一个插件activity实例
//相当于这个时候就用插件apk中的activity代替了预置的<activity android:name=".B$1"android:launchMode="singleTop"/>
//之所以要这样操作,是因为如果不预置activity,即不在manifest中声明,那么就会报activityNotFound的错误。
//所以采取的策略是,先骗过系统,防止出现activityNotFound的错误 Activity activity =mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
try {
// for 4.1+
//通过反射修改mResources为加载了插件资源的resource
ReflectUtil.setField(ContextThemeWrapper.class, activity,"mResources", plugin.getResources());
} catch (Exception ignored) {
// ignored.
}
return activity;
}
}
return mBase.newActivity(cl, className, intent);
}
至此,插件apk中的activity的启动就分析完毕了。
针对service,是hook了系统的利用动态代理实现的。
/**
*hookSystemServices, but need to compatible with Android O in future.
*/
private void hookSystemServices() {
try {
Singleton<IActivityManager> defaultSingleton =(Singleton<IActivityManager>)ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
IActivityManager activityManagerProxy =ActivityManagerProxy.newInstance(this, defaultSingleton.get());
// Hook IActivityManager from ActivityManagerNative
//把singleton的内部的对象用代理对象代替。
ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(),defaultSingleton, "mInstance", activityManagerProxy);
if (defaultSingleton.get() == activityManagerProxy) {
this.mActivityManager = activityManagerProxy;
}
}catch (Exception e) {
e.printStackTrace();
}
}
看下自己定义的动态代理类。
ActivityManagerProxy.java
@Override
publicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("startService".equals(method.getName())) {
try {
return startService(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Start serviceerror", e);
}
}else if ("stopService".equals(method.getName())) {
try {
return stopService(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Stop Service error", e);
}
}else if ("stopServiceToken".equals(method.getName())) {
try {
return stopServiceToken(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Stop service token error", e);
}
}else if ("bindService".equals(method.getName())) {
try {
return bindService(proxy, method, args);
} catch (Throwable e) {
e.printStackTrace();
}
}else if ("unbindService".equals(method.getName())) {
try {
return unbindService(proxy, method, args);
} catch (Throwable e) {
e.printStackTrace();
}
}else if ("getIntentSender".equals(method.getName())) {
try {
getIntentSender(method, args);
} catch (Exception e) {
e.printStackTrace();
}
}else if ("overridePendingTransition".equals(method.getName())){
try {
overridePendingTransition(method, args);
} catch (Exception e){
e.printStackTrace();
}
}
try {
// sometimes system binder has problems.
return method.invoke(this.mActivityManager, args);
}catch (Throwable th) {
Throwable c = th.getCause();
if (c != null && c instanceof DeadObjectException) {
// retry connect to systembinder
IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);
if (ams != null) {
IActivityManager am =ActivityManagerNative.asInterface(ams);
mActivityManager = am;
}
}
Throwable cause = th;
do {
if (cause instanceof RemoteException) {
throw cause;
}
} while ((cause = cause.getCause()) != null);
throw c != null ? c : th;
}
}
ActivityManagerProxy.java
private Object startService(Object proxy, Methodmethod, Object[] args) throws Throwable {
IApplicationThread appThread = (IApplicationThread) args[0];
Intent target = (Intent) args[1];
//先在插件service集合中查找,找不到就说明是主apk中的service
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
//启动的是主apk中的service
if(null == resolveInfo || null == resolveInfo.serviceInfo) {
// is host service
return method.invoke(this.mActivityManager, args);
}
return startDelegateServiceForTarget(target, resolveInfo.serviceInfo,null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}
wrapperTargetIntent():
private Intent wrapperTargetIntent(Intenttarget, ServiceInfo serviceInfo, Bundle extras, int command) {
//fill in service with ComponentName
target.setComponent(new ComponentName(serviceInfo.packageName,serviceInfo.name));
String pluginLocation =mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
//start delegate service to run plugin service inside
boolean local = PluginUtil.isLocalService(serviceInfo);
Class<? extends Service> delegate = local ? LocalService.class :RemoteService.class;
Intent intent = new Intent();
//启动本地代理service
intent.setClass(mPluginManager.getHostContext(), delegate);
intent.putExtra(RemoteService.EXTRA_TARGET, target);
intent.putExtra(RemoteService.EXTRA_COMMAND, command);
intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
if(extras != null) {
intent.putExtras(extras);
}
return intent;
}
以其中的一个service作为例子来看:
/*
*Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd.All rights reserved.
*
*Licensed under the Apache License, Version 2.0 (the "License");
* you maynot use this file except in compliance with the License.
* You mayobtain a copy of the License at
*
*http://www.apache.org/licenses/LICENSE-2.0
*
* Unlessrequired by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUTWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See theLicense for the specific language governing permissions and
*limitations under the License.
*/
package com.didi.virtualapk.delegate;
import android.app.ActivityThread;
import android.app.Application;
import android.app.IActivityManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import com.didi.virtualapk.PluginManager;
importcom.didi.virtualapk.internal.LoadedPlugin;
import com.didi.virtualapk.utils.PluginUtil;
import com.didi.virtualapk.utils.ReflectUtil;
import java.lang.reflect.Method;
/**
* @authorjohnsonlee
*/
public class LocalService extends Service {
private static final String TAG = "LocalService";
/**
* Thetarget service, usually it's a plugin service intent
*/
publicstatic final String EXTRA_TARGET = "target";
publicstatic final String EXTRA_COMMAND = "command";
publicstatic final String EXTRA_PLUGIN_LOCATION = "plugin_location";
publicstatic final int EXTRA_COMMAND_START_SERVICE = 1;
publicstatic final int EXTRA_COMMAND_STOP_SERVICE = 2;
publicstatic final int EXTRA_COMMAND_BIND_SERVICE = 3;
publicstatic final int EXTRA_COMMAND_UNBIND_SERVICE = 4;
private PluginManager mPluginManager;
@Override
publicIBinder onBind(Intent intent) {
return new Binder();
}
@Override
publicvoid onCreate() {
super.onCreate();
mPluginManager = PluginManager.getInstance(this);
}
@Override
publicint onStartCommand(Intent intent, int flags, int startId) {
if(null == intent || !intent.hasExtra(EXTRA_TARGET) ||!intent.hasExtra(EXTRA_COMMAND)) {
return START_STICKY;
}
//获取目标service
Intent target = intent.getParcelableExtra(EXTRA_TARGET);
int command = intent.getIntExtra(EXTRA_COMMAND, 0);
if(null == target || command <= 0) {
return START_STICKY;
}
ComponentName component = target.getComponent();
LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
//ClassNotFoundException when unmarshalling in Android 5.1
target.setExtrasClassLoader(plugin.getClassLoader());
switch (command) {
case EXTRA_COMMAND_START_SERVICE: {
ActivityThread mainThread =(ActivityThread)ReflectUtil.getActivityThread(getBaseContext());
IApplicationThread appThread = mainThread.getApplicationThread();
Service service;
if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)){
service =this.mPluginManager.getComponentsHandler().getService(component);
} else {
try {
service = (Service)plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
Application app =plugin.getApplication();
IBinder token =appThread.asBinder();
Method attach =service.getClass().getMethod("attach", Context.class,ActivityThread.class, String.class, IBinder.class, Application.class,Object.class);
IActivityManager am =mPluginManager.getActivityManager();
attach.invoke(service,plugin.getPluginContext(), mainThread, component.getClassName(), token, app,am);
service.onCreate();
this.mPluginManager.getComponentsHandler().rememberService(component,service);
} catch (Throwable t) {
return START_STICKY;
}
}
service.onStartCommand(target, 0,this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
break;
}
…………
}