让我们来聊一聊插件化吧---高深莫测
来源:互联网 发布:2017骂人网络流行词 编辑:程序博客网 时间:2024/06/06 06:36
现如今插件化的思想和应用在Android上越来越多了,各式各样的方案也是层出不穷,这篇文章旨在告诉大家插件化的核心思想是什么,又有什么样的实现方式。
前言
首先,这篇文章的题目为什么不沿用我之前xxxx!xxxxx这样的风格呢,因为我觉得这样风格太中二了。。
其次,我写这篇文章的原因是因为前些时候看到有大神写了一篇文章Android 插件化的 过去 现在 未来,里面的内容很不错,特别是有一些关于原理的东西,让我回想起当时看几个插件化框架的源码的时候产生的心得和体会,这里也是写出来给大家做一个分享吧。
插件化介绍
在开始真正讲解插件化之前,让我先告诉那些不了解插件化是什么的同学[什么是插件化]。
所谓插件化,就是让我们的应用不必再像原来一样把所有的内容都放在一个apk中,可以把一些功能和逻辑单独抽出来放在插件apk中,然后主apk做到[按需调用],这样的好处是一来可以减少主apk的体积,让应用更轻便,二来可以做到热插拔,更加动态化。
在后文中,我首先会对插件化的实现原理进行一个分析,接着我挑了其中两个比较有代表性的,[Small]和[DroidPlugin]来分析他们的实现有什么区别。
原理分析
在分析原理之前,让我们先想想做插件化会遇到的挑战。大家可以想一想,如果我要做一个插件apk,里面会包含什么?首先想到的肯定是activity和对应的资源,我们要做的事就是在主apk中调用插件apk中的activity,并且加载对应的资源。当然这只是其中的一个挑战,这里我不会带大家分析所有的原理,因为这样一天一夜都讲不完,所以我选取了其中最具代表性的一点:[主apk如何加载插件apk中的activity]。
首先,我们回想一下平时我们是怎么唤起一个activity的:
12
Intent intent = new Intent(ActivityA.this,ActivityB.class);startActivity(intent);
我们调用的是activity的startActivity方法,而我们都知道我们的activity是继承自ContextThemeWrapper的,而ContextThemeWrapper只是一个包装类,真正的逻辑在ContextImpl中,让我们看看其中做了什么。
12345678910111213
public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity)null, intent, -1, options);}
其中调用了mMainThread.getInstrumentation()获取一个Instrumentation对象,这个对象大家可以看作是activity的管家,对activity的操作都会调用它去执行。让我们看看它的execStartActivity方法。
12345678910111213141516171819202122232425262728293031
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); for (int i=0; i<N; i++) { final ActivityMonitor am = mActivityMonitors.get(i); if (am.match(who, null, intent)) { am.mHits++; if (am.isBlocking()) { return requestCode >= 0 ? am.getResult() : null; } break; } } } } try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); 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) { } return null;}
这个方法最关键的一点就是调用了ActivityManagerNative.getDefault()去获取一个ActivityManagerNative对象并且调用了它的startActivity方法。
1
public abstract class ActivityManagerNative extends Binder implements IActivityManager
从它的定义就可以看出它是和aidl相关的,对于aidl这里我不细讲,大家可以自行查阅相关资料,概括来说就是Android中一种跨进程的通信方式。
那既然是跨进程的,通过ActivityManagerNative跨到了哪个进程呢?答案是ActivityManagerService,简称AMS,它是ActivityManagerNative的实现类。是Android framework层最最最重要的几个类之一,是运行在Android内核进程的。下面让我们看看AMS的startActivity方法。
1 23456789
public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());}
可以看到调用了startActivityAsUser。而在startActivityAsUser方法中调用了ActivityStackSupervisor的startActivityMayWait方法。
12345678910
final int startActivityMayWait(IApplicationThread caller, int callingUid,String callingPackage, Intent intent, String resolvedType, IBinder resultTo,String resultWho, int requestCode, int startFlags, String profileFile,ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,Bundle options, int userId) { .......... int res = startActivityLocked(caller, intent, resolvedType,aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,callingPackage, startFlags, options, componentSpecified, null); ......... }}
这个方法内容很多,前面主要是对一些权限的判断,这个我们等等再讲,而在判断完权限之后,调用了startActivityLocked方法。
在调用了startActivityLocked方法之后,是一系列和ActivityStack这个类的交互,这其中的过程我这里不分析了,从ActivityStack这个类的名字就可以看出它是和Activity栈相关的,交互的主要目的也就是处理activity栈的需求。最后会调用到realStartActivityLocked方法。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
final boolean realStartActivityLocked(ActivityRecord r,ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { r.startFreezingScreenLocked(app, 0); if (false) Slog.d(TAG, "realStartActivity: setting app visibility true"); mWindowManager.setAppVisibility(r.appToken, true); // schedule launch ticks to collect information about slow apps. r.startLaunchTickingLocked(); // Have the window manager re-evaluate the orientation of // the screen based on the new activity order. Note that // as a result of this, it can call back into the activity // manager with a new orientation. We don't care about that, // because the activity is not currently running so we are // just restarting it anyway. if (checkConfig) { Configuration config = mWindowManager.updateOrientationFromAppTokens( mService.mConfiguration, r.mayFreezeScreenLocked(app) ? r.appToken : null); mService.updateConfigurationLocked(config, r, false, false); } r.app = app; app.waitingToKill = null; r.launchCount++; r.lastLaunchTime = SystemClock.uptimeMillis(); if (localLOGV) Slog.v(TAG, "Launching: " + r); int idx = app.activities.indexOf(r); if (idx < 0) { app.activities.add(r); } mService.updateLruProcessLocked(app, true, true); final ActivityStack stack = r.task.stack; try { if (app.thread == null) { throw new RemoteException(); } List<ResultInfo> results = null; List<Intent> newIntents = null; if (andResume) { results = r.results; newIntents = r.newIntents; } if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r + " icicle=" + r.icicle + " with results=" + results + " newIntents=" + newIntents + " andResume=" + andResume); if (andResume) { EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, r.userId, System.identityHashCode(r), r.task.taskId, r.shortComponentName); } if (r.isHomeActivity() && r.isNotResolverActivity()) { // Home process is the root process of the task. mService.mHomeProcess = r.task.mActivities.get(0).app; } mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); r.sleeping = false; r.forceNewConfig = false; mService.showAskCompatModeDialogLocked(r); r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); String profileFile = null; ParcelFileDescriptor profileFd = null; boolean profileAutoStop = false; if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) { if (mService.mProfileProc == null || mService.mProfileProc == app) { mService.mProfileProc = app; profileFile = mService.mProfileFile; profileFd = mService.mProfileFd; profileAutoStop = mService.mAutoStopProfiler; } } app.hasShownUi = true; app.pendingUiClean = true; if (profileFd != null) { try { profileFd = profileFd.dup(); } catch (IOException e) { if (profileFd != null) { try { profileFd.close(); } catch (IOException o) { } profileFd = null; } } } app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP); app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration), r.compat, app.repProcState, r.icicle, results, newIntents, !andResume, mService.isNextTransitionForward(), profileFile, profileFd, profileAutoStop); if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { // This may be a heavy-weight process! Note that the package // manager will ensure that only activity can run in the main // process of the .apk, which is the only thing that will be // considered heavy-weight. if (app.processName.equals(app.info.packageName)) { if (mService.mHeavyWeightProcess != null && mService.mHeavyWeightProcess != app) { Slog.w(TAG, "Starting new heavy weight process " + app + " when already running " + mService.mHeavyWeightProcess); } mService.mHeavyWeightProcess = app; Message msg = mService.mHandler.obtainMessage( ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); msg.obj = r; mService.mHandler.sendMessage(msg); } } } catch (RemoteException e) { if (r.launchFailed) { // This is the second time we failed -- finish activity // and give up. Slog.e(TAG, "Second failure launching " + r.intent.getComponent().flattenToShortString() + ", giving up", e); mService.appDiedLocked(app, app.pid, app.thread); stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, "2nd-crash", false); return false; } // This is the first time we failed -- restart process and // retry. app.activities.remove(r); throw e; } r.launchFailed = false; if (stack.updateLRUListLocked(r)) { Slog.w(TAG, "Activity " + r + " being launched, but already in LRU list"); } if (andResume) { // As part of the process of launching, ActivityThread also performs // a resume. stack.minimalResumeActivityLocked(r); } else { // This activity is not starting in the resumed state... which // should look like we asked it to pause+stop (but remain visible), // and it has done so and reported back the current icicle and // other state. if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (starting in stopped state)"); r.state = ActivityState.STOPPED; r.stopped = true; } // Launch the new version setup screen if needed. We do this -after- // launching the initial activity (that is, home), so that it can have // a chance to initialize itself while in the background, making the // switch back to it faster and look better. if (isFrontStack(stack)) { mService.startSetupActivityLocked(); } return true;}
这么长的方法,看的头都晕了,不过没关系,我们看重点的。
1234
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,System.identityHashCode(r), r.info,new Configuration(mService.mConfiguration), r.compat,app.repProcState, r.icicle, results, newIntents, !andResume,mService.isNextTransitionForward(), profileFile, profileFd,profileAutoStop);
重点来了,调用了ApplicationThread的scheduleLaunchActivity方法。大家千万不要被这个类的名字所迷惑了,以为他是一个线程。其实它不仅是线程,还是一个Binder对象,也是和aidl相关的,实现了IApplicationThread接口,定义在ActivityThread类的内部。那我们为什么要用它呢?回想一下,刚刚在Instrumentation里调用了AMS的startActivity之后的所有操作,都是在系统进程中进行的,而现在我们要返回到我们自己的app进程,同样是跨进程,我们需要一个aidl框架去完成,所以这里才会有ApplicationThread。让我们看看具体的方法吧。
123456789101112131415161718192021222324252627
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,int procState, Bundle state, List<ResultInfo> pendingResults,List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { updateProcessState(procState, false); ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.ident = ident; r.intent = intent; r.activityInfo = info; r.compatInfo = compatInfo; r.state = state; r.pendingResults = pendingResults; r.pendingIntents = pendingNewIntents; r.startsNotResumed = notResumed; r.isForward = isForward; r.profileFile = profileName; r.profileFd = profileFd; r.autoStopProfiler = autoStopProfiler; updatePendingConfiguration(curConfig); queueOrSendMessage(H.LAUNCH_ACTIVITY, r); }
在最后调用了queueOrSendMessage(H.LAUNCH_ACTIVITY, r)这个方法。
而这里的H其实是一个handler,queueOrSendMessage方法的作用就是通过handler去send一个message。
123456789101112131415
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"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; ..........}
我们看H的handleMessage,如果message是我们刚刚发送的LAUNCH_ACTIVITY,则调用handleLaunchActivity方法。而在这个方法中,调用了performLaunchActivity去创建一个Activity。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } ComponentName component = r.intent.getComponent(); if (component == null) { component = r.intent.resolveActivity( mInitialApplication.getPackageManager()); r.intent.setComponent(component); } if (r.activityInfo.targetActivity != null) { component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity); } Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); if (r.state != null) { r.state.setClassLoader(cl); } } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config); if (customIntent != null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { activity.setTheme(theme); } activity.mCalled = false; mInstrumentation.callActivityOnCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } r.activity = activity; r.stopped = true; if (!r.activity.mFinished) { activity.performStart(); r.stopped = false; } if (!r.activity.mFinished) { if (r.state != null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); } } if (!r.activity.mFinished) { activity.mCalled = false; mInstrumentation.callActivityOnPostCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPostCreate()"); } } } r.paused = true; mActivities.put(r.token, r); } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); } } return activity;}
方法比较长,其中核心的内容是:
12345678910
try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); if (r.state != null) { r.state.setClassLoader(cl); }}
又调用了Instrumentation的newActivity去创建一个Activity。
至此,启动一个activity的过程就分析完了,让我们来总结一下。
(1) 我们app中是使用了Activity的startActivity方法,具体调用的是ContextImpl的同名函数。
(2) ContextImpl中会调用Instrumentation的execStartActivity方法。
(3) Instrumentation通过aidl进行跨进程通信,最终调用AMS的startActivity方法。
(4) 在系统进程中AMS中先判断权限,然后通过调用ActivityStackSupervisor和ActivityStack进行一系列的交互用来确定Activity栈的使用方式。
(5) 通过ApplicationThread进行跨进程通信,转回到app进程。
(6) 通过ActivityThread中的H(一个handler)传递消息,最终调用Instrumentation来创建一个Activity。
其实如果大家看过其他有关Android系统的调用,比如启动一个Service之类的,整个过程都是大同小异的,无非就是跨进程和AMS,WMS或者PMS进行通信,然后通过ApplicationThread回到app进程最后通过handler传递消息。
好了,说了这么多,这和我们的插件化有什么关系呢?大家想一想,如果我们要在主apk中启动一个插件apk的Activity,上面的哪一步会出问题?大家好好想一想,想一想,一想,想。。。。
没错!就是(4),第四步,权限验证会通不过,为啥呢?因为我们主apk的manifest中没有定义插件apk的Activity啊!
让我们回到代码,看看ActivityStackSupervisor的startActivityMayWait方法,关于权限验证的内容都在里面。
12345678910111213141516
if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. err = ActivityManager.START_CLASS_NOT_FOUND;}if (err != ActivityManager.START_SUCCESS) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Activity.RESULT_CANCELED, null); } setDismissKeyguard(false); ActivityOptions.abort(options); return err;}
在这个方法中有这么一段,而在Instrumentation中会去检查这个值。
1234567891011121314151617
public static void checkStartActivityResult(int res, Object intent) {if (res >= ActivityManager.START_SUCCESS) { return; } switch (res) { case ActivityManager.START_INTENT_NOT_RESOLVED: case ActivityManager.START_CLASS_NOT_FOUND: if (intent instanceof Intent && ((Intent)intent).getComponent() != null) throw new ActivityNotFoundException( "Unable to find explicit activity class " + ((Intent)intent).getComponent().toShortString() + "; have you declared this activity in your AndroidManifest.xml?"); throw new ActivityNotFoundException( "No Activity found to handle " + intent); .....}
如果找不到对应的Activity,直接抛出错误。
唔。。怎么办呢?找不到Activity,也许你会觉得直接在主apk的manifest中实现定义就好了,但是这是不可能的,因为你怎么知道插件apk中有什么Activity呢?如果以后要动态的修改插件apk中的Activity,难道你的主apk也要对应的一次次修改吗?
不要慌,办法都是人想出来的,让我们看看Samll和DroidPlugin的解决办法吧。
思考解决方案
首先我们要明确,要解决的核心问题是[如何能让没有在manifst中注册的Activity能启动起来]。由于权限验证机制是系统做的,我们肯定是没办法修改的,既然我们没办法修改,那是不是考虑去欺骗呢?也就是说可以在manifest中预先定义好几个Activity,俗称占坑,比如名字就叫ActivityA,ActivityB,在校验权限之前把我们插件apk中的Activity替换成定义好的Activity,这样就能顺利通过校验,而在之后真正生成Activity的地方再换回来,瞒天过海。
那怎么去欺骗呢?回归前面的代码,其实答案已经呼之欲出了——我们可以有两种选择,hook Instrumentation或者hook ActivityManagerNative。这也正好对应了Small和DroidPlugin的实现方案。
Small实现方式
首先,让我们看一下Small的实现方式,大家可以去它的GitHub上下载源码。
上文提到,Samll的实现方式是hook Instrumentation,让我们从代码上来看,我们看它的ApkBundleLauncher类,它内部有一个setUp方法。
1234567891011121314151617181920212223242526
public void setUp(Context context) { super.setUp(context); // Inject instrumentation if (sHostInstrumentation == null) { try { final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); final Method method = activityThreadClass.getMethod("currentActivityThread"); Object thread = method.invoke(null, (Object[]) null); Field field = activityThreadClass.getDeclaredField("mInstrumentation"); field.setAccessible(true); sHostInstrumentation = (Instrumentation) field.get(thread); Instrumentation wrapper = new InstrumentationWrapper(); field.set(thread, wrapper); if (context instanceof Activity) { field = Activity.class.getDeclaredField("mInstrumentation"); field.setAccessible(true); field.set(context, wrapper); } } catch (Exception ignored) { ignored.printStackTrace(); // Usually, cannot reach here } }}
可以看到它通过反射获取了Instrumentation并且赋值成了自定义的InstrumentationWrapper。
而在自己定义的InstrumentationWrapper中,会去重写两个重要的方法,那就是execStartActivity和newActivity这两个方法。为什么会选择这两个方法呢?因为我们前面说过,在第一个方法中,系统会去校验权限,而第二个方法则是真正生成Activity实例的方法,我们通过重写两个方法,可以做到我们之前提到的[在execStartActivity的时候把Activity替换成自己的],而[在newActivity又换回成真正的Activity],从而做到[欺骗]的效果。
12345678910111213141516171819
/** @Override V21+ * Wrap activity from REAL to STUB */public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, android.os.Bundle options) { wrapIntent(intent); return ReflectAccelerator.execStartActivityV21(sHostInstrumentation, who, contextThread, token, target, intent, requestCode, options);}/** @Override V20- * Wrap activity from REAL to STUB */public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { wrapIntent(intent); return ReflectAccelerator.execStartActivityV20(sHostInstrumentation, who, contextThread, token, target, intent, requestCode);}
可以看到Wrapper首先根据sdk版本重写了两个不同的方法,这里我们只关注一个就可以,先看wrapIntent方法。
123456789101112131415
private void wrapIntent(Intent intent) { ComponentName component = intent.getComponent(); if (component == null) return; // ignore system intent String realClazz = intent.getComponent().getClassName(); if (sLoadedActivities == null) return; ActivityInfo ai = sLoadedActivities.get(realClazz); if (ai == null) return; // Carry the real(plugin) class for incoming `newActivity' method. intent.addCategory(REDIRECT_FLAG + realClazz); String stubClazz = dequeueStubActivity(ai, realClazz); intent.setComponent(new ComponentName(Small.getContext(), stubClazz));}
这个方法的神奇之处在于它先获取了真正的Activity的信息并且保存起来,然后调用了dequeueStubActivity方法去生成一个[占坑]的Activity。
123456789101112131415161718192021222324252627282930313233
private String dequeueStubActivity(ActivityInfo ai, String realActivityClazz) { if (ai.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { // In standard mode, the stub activity is reusable. return STUB_ACTIVITY_PREFIX; } int availableId = -1; int stubId = -1; int countForMode = STUB_ACTIVITIES_COUNT; int countForAll = countForMode * 3; // 3=[singleTop, singleTask, singleInstance] if (mStubQueue == null) { // Lazy init mStubQueue = new String[countForAll]; } int offset = (ai.launchMode - 1) * countForMode; for (int i = 0; i < countForMode; i++) { String usedActivityClazz = mStubQueue[i + offset]; if (usedActivityClazz == null) { if (availableId == -1) availableId = i; } else if (usedActivityClazz.equals(realActivityClazz)) { stubId = i; } } if (stubId != -1) { availableId = stubId; } else if (availableId != -1) { mStubQueue[availableId + offset] = realActivityClazz; } else { // TODO: Log.e(TAG, "Launch mode " + ai.launchMode + " is full"); } return STUB_ACTIVITY_PREFIX + ai.launchMode + availableId;}
可以看到其中根据真正的Activity的launchMode等因素生成一个占坑Activity,而这些占坑Activity都是定义在manifest中的。
123456789101112131415161718192021222324252627
<application> <!-- Stub Activities --> <!-- 1 standard mode --> <activity android:name=".A" android:launchMode="standard"/> <!-- 4 singleTask mode --> <activity android:name=".A10" android:launchMode="singleTask"/> <activity android:name=".A11" android:launchMode="singleTask"/> <activity android:name=".A12" android:launchMode="singleTask"/> <activity android:name=".A13" android:launchMode="singleTask"/> <!-- 4 singleTop mode --> <activity android:name=".A20" android:launchMode="singleTop"/> <activity android:name=".A21" android:launchMode="singleTop"/> <activity android:name=".A22" android:launchMode="singleTop"/> <activity android:name=".A23" android:launchMode="singleTop"/> <!-- 4 singleInstance mode --> <activity android:name=".A30" android:launchMode="singleInstance"/> <activity android:name=".A31" android:launchMode="singleInstance"/> <activity android:name=".A32" android:launchMode="singleInstance"/> <activity android:name=".A33" android:launchMode="singleInstance"/> <!-- Web Activity --> <activity android:name=".webkit.WebActivity" android:screenOrientation="portrait" android:windowSoftInputMode="stateHidden|adjustPan"/> <!--<service android:name="net.wequick.small.service.UpgradeService"--> <!--android:exported="false"/>--></application>
通过这样的方式我们就顺利的完成了[瞒天过海]的第一步,在之后AMS的权限校验中就能顺利通过了。接着让我们来看newActivity方法。
123456789101112131415161718192021222324252627282930
/** Unwrap activity from STUB to REAL */public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { // Stub -> Real if (!className.startsWith(STUB_ACTIVITY_PREFIX)) { return super.newActivity(cl, className, intent); } className = unwrapIntent(intent, className); Activity activity = super.newActivity(cl, className, intent); return activity;}private String unwrapIntent(Intent intent, String className) { Set<String> categories = intent.getCategories(); if (categories == null) return className; // Get plugin activity class name from categories Iterator<String> it = categories.iterator(); String realClazz = null; while (it.hasNext()) { String category = it.next(); if (category.charAt(0) == REDIRECT_FLAG) { realClazz = category.substring(1); break; } } if (realClazz == null) return className; return realClazz;}
这里就更简单了,通过unwrapIntent获取真正的Activity并且调用父类的newActivity方法,也就是Instrumentation去生成一个Activity。
到这儿,Small的解决方案就基本讲完了,原理是很简单的,就是通过反射替换掉Instrumentation,然后在里面做文章。
DroidPlugin实现方式
DroidPlugin是另外一种插件化框架,它采取的方案是hook整个ActivityManagerNative。
首先让我们看它其中的IActivityManagerHook类。
1
public class IActivityManagerHook extends ProxyHook
它继承自ProxyHook。
1234567891011121314151617
public abstract class ProxyHook extends Hook implements InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (!isEnable()) { return method.invoke(mOldObj, args); } HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method); if (hookedMethodHandler != null) { return hookedMethodHandler.doHookInner(mOldObj, method, args); } return method.invoke(mOldObj, args); } ...........}
ProxyHook实现了InvocationHandler接口,也就是说它是用于动态代理的,在invoke方法中先通过mHookHandles去获取对应的hookedMethodHandler,这里的mHookHandles在我们对应的情况下是IActivityManagerHookHandle。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
public class IActivityManagerHookHandle extends BaseHookHandle { protected void init() { sHookedMethodHandlers.put("startActivity", new startActivity(mHostContext)); sHookedMethodHandlers.put("startActivityAsUser", new startActivityAsUser(mHostContext)); sHookedMethodHandlers.put("startActivityAsCaller", new startActivityAsCaller(mHostContext)); sHookedMethodHandlers.put("startActivityAndWait", new startActivityAndWait(mHostContext)); sHookedMethodHandlers.put("startActivityWithConfig", new startActivityWithConfig(mHostContext)); sHookedMethodHandlers.put("startActivityIntentSender", new startActivityIntentSender(mHostContext)); sHookedMethodHandlers.put("startVoiceActivity", new startVoiceActivity(mHostContext)); sHookedMethodHandlers.put("startNextMatchingActivity", new startNextMatchingActivity(mHostContext)); sHookedMethodHandlers.put("startActivityFromRecents", new startActivityFromRecents(mHostContext)); sHookedMethodHandlers.put("finishActivity", new finishActivity(mHostContext)); sHookedMethodHandlers.put("registerReceiver", new registerReceiver(mHostContext)); sHookedMethodHandlers.put("broadcastIntent", new broadcastIntent(mHostContext)); sHookedMethodHandlers.put("unbroadcastIntent", new unbroadcastIntent(mHostContext)); sHookedMethodHandlers.put("getCallingPackage", new getCallingPackage(mHostContext)); sHookedMethodHandlers.put("getCallingActivity", new getCallingActivity(mHostContext)); sHookedMethodHandlers.put("getAppTasks", new getAppTasks(mHostContext)); sHookedMethodHandlers.put("addAppTask", new addAppTask(mHostContext)); sHookedMethodHandlers.put("getTasks", new getTasks(mHostContext)); sHookedMethodHandlers.put("getServices", new getServices(mHostContext)); sHookedMethodHandlers.put("getProcessesInErrorState", new getProcessesInErrorState(mHostContext)); sHookedMethodHandlers.put("getContentProvider", new getContentProvider(mHostContext)); sHookedMethodHandlers.put("getContentProviderExternal", new getContentProviderExternal(mHostContext)); sHookedMethodHandlers.put("removeContentProviderExternal", new removeContentProviderExternal(mHostContext)); sHookedMethodHandlers.put("publishContentProviders", new publishContentProviders(mHostContext)); sHookedMethodHandlers.put("getRunningServiceControlPanel", new getRunningServiceControlPanel(mHostContext)); sHookedMethodHandlers.put("startService", new startService(mHostContext)); sHookedMethodHandlers.put("stopService", new stopService(mHostContext)); sHookedMethodHandlers.put("stopServiceToken", new stopServiceToken(mHostContext)); sHookedMethodHandlers.put("setServiceForeground", new setServiceForeground(mHostContext)); sHookedMethodHandlers.put("bindService", new bindService(mHostContext)); sHookedMethodHandlers.put("publishService", new publishService(mHostContext)); sHookedMethodHandlers.put("unbindFinished", new unbindFinished(mHostContext)); sHookedMethodHandlers.put("peekService", new peekService(mHostContext)); sHookedMethodHandlers.put("bindBackupAgent", new bindBackupAgent(mHostContext)); sHookedMethodHandlers.put("backupAgentCreated", new backupAgentCreated(mHostContext)); sHookedMethodHandlers.put("unbindBackupAgent", new unbindBackupAgent(mHostContext)); sHookedMethodHandlers.put("killApplicationProcess", new killApplicationProcess(mHostContext)); sHookedMethodHandlers.put("startInstrumentation", new startInstrumentation(mHostContext)); sHookedMethodHandlers.put("getActivityClassForToken", new getActivityClassForToken(mHostContext)); sHookedMethodHandlers.put("getPackageForToken", new getPackageForToken(mHostContext)); sHookedMethodHandlers.put("getIntentSender", new getIntentSender(mHostContext)); sHookedMethodHandlers.put("clearApplicationUserData", new clearApplicationUserData(mHostContext)); sHookedMethodHandlers.put("handleIncomingUser", new handleIncomingUser(mHostContext)); sHookedMethodHandlers.put("grantUriPermission", new grantUriPermission(mHostContext)); sHookedMethodHandlers.put("getPersistedUriPermissions", new getPersistedUriPermissions(mHostContext)); sHookedMethodHandlers.put("killBackgroundProcesses", new killBackgroundProcesses(mHostContext)); sHookedMethodHandlers.put("forceStopPackage", new forceStopPackage(mHostContext)); sHookedMethodHandlers.put("getRunningAppProcesses", new getRunningAppProcesses(mHostContext)); sHookedMethodHandlers.put("getRunningExternalApplications", new getRunningExternalApplications(mHostContext)); sHookedMethodHandlers.put("getMyMemoryState", new getMyMemoryState(mHostContext)); sHookedMethodHandlers.put("crashApplication", new crashApplication(mHostContext)); sHookedMethodHandlers.put("grantUriPermissionFromOwner", new grantUriPermissionFromOwner(mHostContext)); sHookedMethodHandlers.put("checkGrantUriPermission", new checkGrantUriPermission(mHostContext)); sHookedMethodHandlers.put("startActivities", new startActivities(mHostContext)); sHookedMethodHandlers.put("getPackageScreenCompatMode", new getPackageScreenCompatMode(mHostContext)); sHookedMethodHandlers.put("setPackageScreenCompatMode", new setPackageScreenCompatMode(mHostContext)); sHookedMethodHandlers.put("getPackageAskScreenCompat", new getPackageAskScreenCompat(mHostContext)); sHookedMethodHandlers.put("setPackageAskScreenCompat", new setPackageAskScreenCompat(mHostContext)); sHookedMethodHandlers.put("navigateUpTo", new navigateUpTo(mHostContext)); sHookedMethodHandlers.put("serviceDoneExecuting", new serviceDoneExecuting(mHostContext)); }}
可以看到在init中生成了很多类,而我们在invoke方法中就会根据对应的方法名拿到对应的类。
回想一下,我们在这里对应的是什么方法?根据前面的文章,我们知道是startActivity方法,进而拿到的是startActivity类。
回到invoke方法,拿到HookedMethodHandler后,会执行它的doInnerHook方法。
1234567891011121314151617181920212223
public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable { long b = System.currentTimeMillis(); try { mUseFakedResult = false; mFakedResult = null; boolean suc = beforeInvoke(receiver, method, args); Object invokeResult = null; if (!suc) { invokeResult = method.invoke(receiver, args); } afterInvoke(receiver, method, args, invokeResult); if (mUseFakedResult) { return mFakedResult; } else { return invokeResult; } } finally { long time = System.currentTimeMillis() - b; if (time > 5) { Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time); } }}
可以看到这个方法也是AOP的,在真正的调用method.invoke之前和之后会对应的调用beforeInvoke和afterInvoke。这两个方法是有待HookedMethodHandler的子类去实现的,这里是startActivity这个子类。
1234567891011121314151617
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable { RunningActivities.beforeStartActivity(); boolean bRet; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { bRet = doReplaceIntentForStartActivityAPILow(args); } else { bRet = doReplaceIntentForStartActivityAPIHigh(args); } if (!bRet) { setFakedResult(Activity.RESULT_CANCELED); return true; } return super.beforeInvoke(receiver, method, args);}
根据sdk版本进入不同的方法,这里我们只看APIHigh。
1234567891011121314151617181920212223242526272829303132333435363738
protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException { int intentOfArgIndex = findFirstIntentIndexInArgs(args); if (args != null && args.length > 1 && intentOfArgIndex >= 0) { Intent intent = (Intent) args[intentOfArgIndex]; //XXX String callingPackage = (String) args[1]; if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) { PluginPatchManager.getInstance().startPluginActivity(intent); return false; } ActivityInfo activityInfo = resolveActivity(intent); if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) { ComponentName component = selectProxyActivity(intent); if (component != null) { Intent newIntent = new Intent(); try { ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName()); setIntentClassLoader(newIntent, pluginClassLoader); } catch (Exception e) { Log.w(TAG, "Set Class Loader to new Intent fail", e); } newIntent.setComponent(component); newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); newIntent.setFlags(intent.getFlags()); String callingPackage = (String) args[1]; if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) { newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); args[intentOfArgIndex] = newIntent; args[1] = mHostContext.getPackageName(); } else { Log.w(TAG, "startActivity,replace selectProxyActivity fail"); } } } return true;}
又是一个逻辑比较多的方法,但是很好理解,其实和Small做的事是差不多的,就是生成一个占坑的Intent,把真正的activity当作参数放在Intent中。
到这儿,就完成了第一步,我们要做的就是在对应的地方Hook掉这个AMS,至于怎么Hook大家自己去看源码,Hook的技巧不是重点,重点是Hook了什么。
那DroidPlugin是在哪里把Activity还原了呢?回想一下前文startActivity步骤6:
(6) 通过ActivityThread中的H(一个handler)传递消息,最终调用Instrumentation来创建一个Activity。
通过Handler去传递消息,让我们看看Handler的源码。
123456789101112
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}
在dispatchMessage中,有一个CallBack,如果它不为空,就使用它去处理而不走Handler的handleMessage方法。DroidPlugin正是利用了这一点,自己去生成了一个CallBack并且通过反射注入到了ActivityThread的H类中。
123456789
public boolean handleMessage(Message msg) { if (msg.what == LAUNCH_ACTIVITY) { return handleLaunchActivity(msg); } ...........}
这是对应CallBack的handleMessage方法,如果message是LAUNCH_ACTIVITY,直接调用了handleLaunchActivity方法。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
private boolean handleLaunchActivity(Message msg) { try { Object obj = msg.obj; Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent"); //ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true); stubIntent.setExtrasClassLoader(mHostContext.getClassLoader()); Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); // 这里多加一个isNotShortcutProxyActivity的判断,因为ShortcutProxyActivity的很特殊,启动它的时候, // 也会带上一个EXTRA_TARGET_INTENT的数据,就会导致这里误以为是启动插件Activity,所以这里要先做一个判断。 // 之前ShortcutProxyActivity错误复用了key,但是为了兼容,所以这里就先这么判断吧。 if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) { IPackageManagerHook.fixContextPackageManager(mHostContext); ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager()); ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0); if (targetActivityInfo != null) { if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) { targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName()); } ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0); ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null; if (stubActivityInfo != null) { PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName); } PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo); ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName()); setIntentClassLoader(targetIntent, pluginClassLoader); setIntentClassLoader(stubIntent, pluginClassLoader); boolean success = false; try { targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo); if (stubActivityInfo != null) { targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo); } success = true; } catch (Exception e) { Log.e(TAG, "putExtra 1 fail", e); } if (!success && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { try { ClassLoader oldParent = fixedClassLoader(pluginClassLoader); targetIntent.putExtras(targetIntent.getExtras()); targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo); if (stubActivityInfo != null) { targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo); } fixedClassLoader(oldParent); success = true; } catch (Exception e) { Log.e(TAG, "putExtra 2 fail", e); } } if (!success) { Intent newTargetIntent = new Intent(); newTargetIntent.setComponent(targetIntent.getComponent()); newTargetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo); if (stubActivityInfo != null) { newTargetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo); } FieldUtils.writeDeclaredField(msg.obj, "intent", newTargetIntent); } else { FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent); } FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo); Log.i(TAG, "handleLaunchActivity OK"); } else { Log.e(TAG, "handleLaunchActivity oldInfo==null"); } } else { Log.e(TAG, "handleLaunchActivity targetIntent==null"); } } catch (Exception e) { Log.e(TAG, "handleLaunchActivity FAIL", e); } if (mCallback != null) { return mCallback.handleMessage(msg); } else { return false; }}
代码依旧很长,但是核心就是拿到真正的Activity并且创建。
至此,DroidPlugin的逻辑也就理完了,和Small不同,它是hook了整个AMS并且通过反射替换了H类的CallBack,做到了[瞒天过海]。
两种方式的比较
那么这两种方式孰优孰劣呢?
我认为,DroidPlugin的方式是更加[屌]的,大家可以看上面IActivityManagerHookHandle这个类,在init方法中put的那些类,和Activity相关的只有一小部分,更多的是service,broadcast等等的操作。这是Small使用[Hook Instrumentation]所做不到的,因为Instrumentation只是Activity的管家,如果涉及到service这样的,它就无能为力了。
但是从另外一个方面说,Service的动态注册这样的需求其实是不多的,Hook Instrumentation基本已能满足大部分的场景的,另外Hook AMS需要的知识储备是要多得多的,拿我来说吧,看DroidPlugin的源码比看Small的源码困难太多了。。
这些都是要大家自己斟酌的。
- 让我们来聊一聊插件化吧---高深莫测
- Xcode8时代让我们一起继续使用我们的插件吧
- ffmpeg高深莫测
- 编程并非高深莫测
- 第5章 高深莫测
- 让我们开始吧
- 让我们反射吧
- 让我们一起挑战吧!
- 让我们开始吧
- 让我们一起来android吧
- 让我们离线吧
- 让我们继续吧
- 让我们抛弃char吧
- 让我们一起努力吧~
- 高深莫测的酒店服务小姐
- 其实闭包并不高深莫测
- PHPer:让我们拥抱敏捷吧
- 让我们来帮助微软吧
- 安卓动态加入xml布局并设置id
- solr查询如何支持多个fq 多条件查询
- linux系统调用、库函数和内核函数关系与区别
- #pragma pack(n)和__attribute__((aligned(m)))的区别
- 邮箱钓鱼那些事:Chrome地址反转漏洞应用案例
- 让我们来聊一聊插件化吧---高深莫测
- 关于Android项目中打包资源文件到apk的总结
- Windows Server 2012 R2安装密钥
- log4j配置祥解
- 数据结构实验之栈一:进制转换
- 动态规划学习笔记
- 安装Centos 6最新详细教程 virtualbox虚拟机
- SDUTACM 数据结构实验之栈六:下一较大值(二)
- 进程间通信