插件化是最近几年的比较流行的技术,最近腾讯出了以Shadow,和其他框架比,官方介绍还是很多的。

这个后面会探索下,一直觉得国内的开发者在我们自己的范围内玩的真的玩出花了(Google play是不允许这些动态化和热修的东西的,否则等着警告吧)。

为了看Shadow我又把之前看动态的文章给找出来,也算是复习了。
对于插件化,就要理解什么是插件化

插件化

插件化技术最初源于免安装运行apk的想法,这个免安装的apk或者dex可以理解为插件。支持插件化的app(宿主App host)可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展(AB
Test 、换肤等)。

对于Activity的hook其实网上有很多文章,讲述怎么切入,今天我们就以滴滴的VirtualAPK为蓝本分析下怎么hook一个Activity.

如何Hook Acitvity

对于一个Activity,我们如何Hook,就需要了解整个Activity的start过程。
为了更好理解Activity的启动过程,我们需要一步步分析不同的启动过程。

首先我们可以从Activity的启动过程讲起。

startActivity过程

我就以目前我常用的android27为基础来一步步跟踪如何启动Activity吧

  1. android.app.Activity 入口是startActivity
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    class Activity {
    @Override
    public void startActivity(Intent intent) {
    this.startActivity(intent, null);
    }


    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
    // 有options参数
    if (options != null) {
    startActivityForResult(intent, -1, options);
    } else {
    // 无options参数
    // Note we want to go through this call for compatibility with
    // applications that may have overridden the method.
    startActivityForResult(intent, -1);
    }
    }



    // 无options参数最后也是默认传个空值
    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
    startActivityForResult(intent, requestCode, null);
    }


    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
    @Nullable Bundle options) {
    if (mParent == null) {
    options = transferSpringboardActivityOptions(options);

    // 通过mInstrumentation来进行下一步
    Instrumentation.ActivityResult ar =
    mInstrumentation.execStartActivity(
    this, mMainThread.getApplicationThread(), mToken, this,
    intent, requestCode, options);
    if (ar != null) {
    mMainThread.sendActivityResult(
    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
    ar.getResultData());
    }
    if (requestCode >= 0) {
    // If this start is requesting a result, we can avoid making
    // the activity visible until the result is received. Setting
    // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
    // activity hidden during this time, to avoid flickering.
    // This can only be done when a result is requested because
    // that guarantees we will get information back when the
    // activity is finished, no matter what happens to it.
    mStartedActivity = true;
    }

    cancelInputsAndStartExitTransition(options);
    // TODO Consider clearing/flushing other event sources and events for child windows.
    } else {
    if (options != null) {
    mParent.startActivityFromChild(this, intent, requestCode, options);
    } else {
    // Note we want to go through this method for compatibility with
    // existing applications that may have overridden it.
    mParent.startActivityFromChild(this, intent, requestCode);
    }
    }
    }


    }

而后若是parent为空,就不在分析了,大同小异。

  1. android.app.Instrumentationde
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Instrumentation {

public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;

// ......省略非关键代码
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
// 这个才是关键
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target, requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
}
  1. 最后还是要到ActivityManager走一遭

通过ServiceManager获取远程服务AMS的Binder,然后通过这个Binder创建一个ActivityManagerProxy,将这个ActivityManagerProxy 对象返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ActivityManager {

public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}

// 这实际上是个单例
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
}

ServiceManager.getService(Context.ACTIVITY_SERVICE)这个我比较好奇,这个IBinder究竟是什么,我们最终定位到了ActivityManagerService

  1. com.android.server.am.ActivityManagerService的.ActivityManagerService
    入口是startActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 继承IActivityManager.Stub
    public class ActivityManagerService extends IActivityManager.Stub
    implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    @Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
    Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
    int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {

    return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
    resultWho, requestCode, startFlags, profilerInfo, bOptions,
    UserHandle.getCallingUserId());
    }


    @Override
    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
    Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
    int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
    enforceNotIsolatedCaller("startActivity");
    userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
    userId, false, ALLOW_FULL_ONLY, "startActivity", null);
    // TODO: Switch to user app stacks here.
    return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
    resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
    profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser");
    }
    }
  2. com.android.server.am.AcitivityStarter.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    public class AcitivityStarter {
    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 globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,
    TaskRecord inTask, String reason) {

    //.....
    ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);

    final ActivityRecord[] outRecord = new ActivityRecord[1];
    int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
    aInfo, rInfo, voiceSession, voiceInteractor,
    resultTo, resultWho, requestCode, callingPid,
    callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
    options, ignoreTargetSecurity, componentSpecified, outRecord, inTask,
    reason);

    }

    int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
    String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
    IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
    IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
    String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
    ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
    ActivityRecord[] outActivity, TaskRecord inTask, String reason) {

    //....

    mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,
    aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
    callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
    options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,
    inTask);

    //....
    }

    private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
    IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
    int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
    ActivityRecord[] outActivity) {
    int result = START_CANCELED;
    try {
    mService.mWindowManager.deferSurfaceLayout();
    // 继续跟踪
    result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
    startFlags, doResume, options, inTask, outActivity);
    } finally {
    //
    }

    postStartActivityProcessing(r, result, mSupervisor.getLastStack().mStackId, mSourceRecord,
    mTargetStack);

    return result;
    }

    // Note: This method should only be called from {@link startActivity}.
    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
    IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
    int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
    ActivityRecord[] outActivity) {
    if (mDoResume) {
    final ActivityRecord topTaskActivity =
    mStartActivity.getTask().topRunningActivityLocked();
    if (!mTargetStack.isFocusable()
    || (topTaskActivity != null && topTaskActivity.mTaskOverlay
    && mStartActivity != topTaskActivity)) {

    mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);

    mWindowManager.executeAppTransition();
    } else {

    if (mTargetStack.isFocusable() && !mSupervisor.isFocusedStack(mTargetStack)) {
    mTargetStack.moveToFront("startActivityUnchecked");
    }
    // 这一步不太好定位啊
    mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
    mOptions);
    }
    } else {
    mTargetStack.addRecentActivityLocked(mStartActivity);
    }
    }


    }
  3. com.android.server.am.ActivityStackSupervisorresumeFocusedStackTopActivityLocked

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener {


    boolean resumeFocusedStackTopActivityLocked(
    ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {

    if (!readyToResume()) {
    return false;
    }

    if (targetStack != null && isFocusedStack(targetStack)) {
    return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
    }

    final ActivityRecord r = mFocusedStack.topRunningActivityLocked();
    if (r == null || r.state != RESUMED) {
    // 栈顶元素
    mFocusedStack.resumeTopActivityUncheckedLocked(null, null);
    } else if (r.state == RESUMED) {
    // Kick off any lingering app transitions form the MoveTaskToFront operation.
    mFocusedStack.executeAppTransition(targetOptions);
    }

    return false;
    }
  4. com.android.server.am.ActivityStack的resumeTopActivityUncheckedLocked

1
2
3
4
5
6
7
8
9
10
11
12
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
if (mStackSupervisor.inResumeTopActivity) {
// Don't even start recursing.
return false;
}

// ...

mStackSupervisor.startSpecificActivityLocked(next, true, true);


}

在ActivityStack.resumeTopActivityInnerLocked方法中会去判断是否有Activity处于Resume状态,如果有的话会先让这个Activity执行Pausing过程,然后再执行startSpecificActivityLocked方法启动要启动Activity。

  1. com.android.server.am.ActivityStackSupervisor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener {
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// .....
// 庐山真面目
realStartActivityLocked(r, app, andResume, checkConfig);

}

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,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, !andResume,
mService.isNextTransitionForward(), profilerInfo);
}
}

app(ProcessRecord的对象)的thread实际上也是一个 IApplicationThread对象,实际上会调用的是应用进程的ApplicationThread。

  1. ApplicationThread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private class ApplicationThread extends IApplicationThread.Stub {

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) {

updateProcessState(procState, false);

ActivityClientRecord r = new ActivityClientRecord();

r.token = token;
r.ident = ident;
...
updatePendingConfiguration(curConfig);

sendMessage(H.LAUNCH_ACTIVITY, r);
}

private void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}

private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 +
"seq= " + seq);
Message msg = Message.obtain();
msg.what = what;
SomeArgs args = SomeArgs.obtain();
args.arg1 = obj;
args.argi1 = arg1;
args.argi2 = arg2;
args.argi3 = seq;
msg.obj = args;
mH.sendMessage(msg);
}
}

mH实际上是个Handler,用来将处理binder信息的消息同步到主线程,可以在ActivityThread里查看H这个内部类。

  1. ActivityThread$H
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private class H extends Handler {
    }
    public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
    case LAUNCH_ACTIVITY: {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

    r.packageInfo = getPackageInfoNoCheck(
    r.activityInfo.applicationInfo, r.compatInfo);
    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    } break;
    }
    }
    }

可以画出时序图如下:

思考的问题

看了刚才的Activity的启动过程,看着很复杂,不过很多地方我们开发中用到的地方很少,对于插件化,我们必须要思考几个问题:

  1. 如何在宿主中加载额外的插件
  2. 如何做到宿主和插件的通信(eg:在宿主中,如何启动一个插件的?)
  3. 如何对不同的启动模式的区分(是栈顶复用 还是栈内复用,还是标准启动模式等)

针对这写问题,下面会一一解决:

如何在宿主中加载额外的插件

这个问题后面会有介绍,暂时不考虑单独加载的问题,本文是入门篇,只考虑加载java文件下有,当时没有在Manifest文件中注册的Activity.

如何启动没在Manifest文件中注册的Activity

若是你在项目中,写了一个Acitivy,当时没有在Manifest文件中注册注册,若是你直接启动会报一个类似这样的异常:

1
Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {cc.cyning.hookactivity/cc.cyning.hookactivity.SecondActivity}; have you declared this activity in your AndroidManifest.xml?

其实我们可以从Activity的启动过程触发,搞一个偷天换日:

在启动时,把需要的启动的Activity(但没有在Manifest文件中注册的Activity,我们为了方便讲述,把它称为插件Activity)在启动前拦截住,把插件Activity替换成在AndroidManifest.xml已经注册的Activity(我们称之为占坑Activity).这样在AMS中能正常的校验。否则会报一个bad token的异常。

当SystemServer启动Activity时,需要去拦截判断下,若是之前用占坑Activity替换的,需要重新把插件Activity再替换回来。
这个就是代考的抢手,考试时枪手上,等考试结束了,被替考者再接着表演。

我们其实可以在启动之前,也就是InstrumentationexecStartActivity步骤中,去hook替换参数中的Intent:

1
2
3
Intent intent1 = new Intent(who, ProxyActivity.class);
intent1.setData(intent.getData());
intent1.putExtra("oldIntent", intent);

ProxyActivity就是我们的占坑Activity,因为它已经在Manifest文件中注册了,AMS校验能通过。

在ApplicationThread处理完消息时,需要再传到AcitityThread的的Handler(也就是上文的H),我们能监听到它的消息类型,若是startActivity,再将占坑Activity替换成插件Activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public boolean handleMessage(Message msg) {
Log.i("HookAmsUtil", "handleMessage");

//替换之前的Intent
if (msg.what == LAUNCH_ACTIVITY) {
Log.i("HookAmsUtil", "lauchActivity");
handleLauchActivity(msg);
}

handler.handleMessage(msg);
return true;
}

private void handleLauchActivity(Message msg) {
Object obj = msg.obj;//ActivityClientRecord
try {
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyInent = (Intent) intentField.get(obj);
Intent realIntent = proxyInent.getParcelableExtra("oldIntent");
// 是占坑Activity替换的
if (realIntent != null) {

// 将插件的intent恢复
proxyInent.setComponent(realIntent.getComponent());
}
} catch (Exception e) {
Log.i("HookAmsUtil", "lauchActivity falied");
}

}

如何对不同的启动模式的区分

若是你给插件Activity设置了非标准启动方式,无论再hookInstrumentation或者ActivityThread$H都无法完美解决,那么怎么能解决不同的启动模式问题呢?
VirtualAPK的解决思路是在Manifest中多加几个启动方式不同的占坑Activity,

总结

本文是从低配版的 VirtualAPK只是hook Activity的启动过程,首先是从startActivity,这个步骤如下:

  1. context的startActiivty
  2. Instrumentation的execStartActivity