Android客戶端的業(yè)務(wù)越來(lái)越多并思,客戶端代碼量也越來(lái)越顯得臃腫庐氮,一般都采用組件化,將應(yīng)用進(jìn)行多個(gè)模塊開(kāi)發(fā)宋彼,但是同樣不會(huì)讓apk瘦起來(lái)弄砍,采用插件化則可以進(jìn)行熱拔插的方式進(jìn)行功能模塊使用起來(lái),現(xiàn)在就為你講解如何啟動(dòng)一個(gè)插件的Activity输涕。
首先我們得了解ClassLoader,Android在API中給出可動(dòng)態(tài)加載的有:DexClassLoader 和 PathClassLoader棚品。
DexClassLoader:可加載jar寄雀、apk和dex写隶,可以從SD卡中加載(本文使用這種方式)
PathClassLoader:只能加載已經(jīng)安裝搭配Android系統(tǒng)中的apk文件
我們先假設(shè)插件MuPlug.apk糙箍,是我們的一個(gè)插件apk,存放到/sdcard/目錄下。
首先在需要加載插件之間合并Dex文件到BaseDexClassLoader.dexElements中(我們的代碼都放到了這里)碴卧。
/**
* Dex代碼注入類
* Created by Hickey on 2017/6/4 on MuDynamicLoadingHost.
*/
public class DexInject {
public static void inject(DexClassLoader dexClassLoader) {
/** 拿到本應(yīng)用的PathClassLoader */
PathClassLoader pathClassLoader = (PathClassLoader) AppContext.getAppContext().getClassLoader();
try {
/** 獲取宿主和插件pathList */
Object mainObj = getPathList(pathClassLoader);
Object plugObj = getPathList(dexClassLoader);
/** 獲取組合之后的dexElements */
Object dexElements = combineArray(getDexElements(mainObj), getDexElements(plugObj));
/** 重新設(shè)置字段值 */
setField(mainObj, mainObj.getClass(), dexElements, "dexElements");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
/**
* 設(shè)置對(duì)象參數(shù)值
*
* @param dexPathList 此類對(duì)象
* @param cls 此類類名
* @param dexElementsValus 字段值
* @param field 字段名稱
* @throws NoSuchMethodException
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private static void setField(Object dexPathList, Class<?> cls, Object dexElementsValus, String field) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
Field set = cls.getDeclaredField(field);
set.setAccessible(true);
set.set(dexPathList, dexElementsValus);
}
/**
* 重新組合數(shù)組
*
* @param main
* @param plug
* @return
*/
private static Object combineArray(Object main, Object plug) {
/** 獲取原數(shù)組類型 */
Class<?> loadClass = main.getClass().getComponentType();
/** 獲取宿主DexElements的長(zhǎng)度 */
int mainLen = Array.getLength(main);
MuL.e("Host dex length:"+mainLen);
/** 現(xiàn)在的長(zhǎng)度 */
int curLen = Array.getLength(plug) + mainLen;
Object result = Array.newInstance(loadClass, curLen);
for (int i = 0; i < curLen; ++i) {
if (i < mainLen) {
Array.set(result, i, Array.get(main, i));
} else {
Array.set(result, i, Array.get(plug, i - mainLen));
}
}
MuL.e("After adding the plugin Dex,length:" + curLen);
return result;
}
/**
* 反射獲取到DexPathList對(duì)象
*
* @param pathClassLoader 類加載
* @return
* @throws ClassNotFoundException
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private static Object getPathList(Object pathClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
return getField(pathClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
/**
* 獲取某個(gè)類的全局變量
*
* @param classLoader 對(duì)象
* @param cls 類
* @param field 字段名稱
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private static Object getField(Object classLoader, Class<?> cls, String field) throws NoSuchFieldException, IllegalAccessException {
Field mField = cls.getDeclaredField(field);
mField.setAccessible(true);
return mField.get(classLoader);
}
/**
* 獲取dexElements
*
* @param mPathList
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private static Object getDexElements(Object mPathList) throws NoSuchFieldException, IllegalAccessException {
return getField(mPathList, mPathList.getClass(), "dexElements");
}
}
我們知道啟動(dòng)Activity一般都需要在清單文件中聲明才可以正常使用弱卡,否則就出現(xiàn)找不到Activity的異常。
在這里我們需要代理 ActivityManagerNative中的IActivityManager對(duì)象
/**
* Retrieve the system's default/global activity manager.
*/
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
public static void onProxyActivityManagerNative(){
try {
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
/** 獲取gDefault的值 */
Field gDefault = activityManagerNativeClass.getDeclaredField("gDefault");
gDefault.setAccessible(true);
Object objSingleton = gDefault.get(null);
/** 獲取Singleton對(duì)象 */
Class<?> clsSingleton = Class.forName("android.util.Singleton");
/** 獲取Singleton T 對(duì)象 */
Field field = clsSingleton.getDeclaredField("mInstance");
field.setAccessible(true);
Object objIActivityManager = field.get(objSingleton);
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{
iActivityManagerInterface
}, new IActivityManagerHandler(objIActivityManager));
field.set(objSingleton, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
這個(gè)IActivityManagerHandler:
/**
* Created by Hickey on 2017/6/4 on MuDynamicLoadingHost.
*/
public class IActivityManagerHandler implements InvocationHandler {
public static final String EXTRA_INTENT = "EXTRA_INTENT";
private Object objIActivityManager;
public IActivityManagerHandler(Object objIActivityManager) {
this.objIActivityManager = objIActivityManager;
}
/**
* 代理某些ActivityManager的某些方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//當(dāng)接收到應(yīng)用使用startActivity方法的時(shí)候
if ("startActivity".equals(method.getName())){
Pair<Integer,Intent> mPair = onFoundFirstIntentOfArgs(args);
/** Create proxy component name */
String pkgName = AppContext.getAppContext().getPackageName();
String clzName = ProxyActivity.class.getName();
ComponentName mComponentName = new ComponentName(pkgName,clzName);
Intent pIntent = new Intent();
pIntent.setComponent(mComponentName);
/** Will save the real intention object */
pIntent.putExtra(EXTRA_INTENT,mPair.second);
/** Replace intention */
args[mPair.first] = pIntent;
}
return method.invoke(objIActivityManager, args);
}
/**
* 獲取對(duì)象和參數(shù)下標(biāo)
* @param args
* @return
*/
private Pair<Integer, Intent> onFoundFirstIntentOfArgs(Object... args) {
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
return Pair.create(index, (Intent) args[index]);
}
}
當(dāng)IActivityManager的startActivity方法被執(zhí)行的時(shí)候
android.app.IActivityManager ;
public interface IActivityManager extends IInterface {
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
}
我們替換掉intent參數(shù)對(duì)象住册,換成我們的ProxyActivity,從而繞過(guò)AMS的檢測(cè)婶博。那我們什么時(shí)候換回來(lái)呢,我們繼續(xù)hook...
我們應(yīng)用都是被ActivityThread的控制來(lái)調(diào)度的,通過(guò)內(nèi)部類H來(lái)進(jìn)行分發(fā)消息的
final H mH = new H();
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
.....
}
所以我們需要?jiǎng)?chuàng)建自己的Handler.CallBack對(duì)象來(lái)處理這些消息
public static void onProxyActivityThreadmH(){
try {
Class<?> cls = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = cls.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
/** 執(zhí)行方法得到ActivityThread對(duì)象 */
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
/** 由于ActivityThread一個(gè)進(jìn)程只有一個(gè),我們獲取這個(gè)對(duì)象的mH */
Field mHField = cls.getDeclaredField("mH");
mHField.setAccessible(true);
/**得到H這個(gè)Handler*/
Handler mH = (Handler) mHField.get(currentActivityThread);
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mH, new ActivityThreadHanderCallBack(mH));
} catch (Exception e) {
e.printStackTrace();
}
}
public class ActivityThreadHanderCallBack implements Handler.Callback{
private Handler mH;
public static final int LAUNCH_ACTIVITY = 100;
public ActivityThreadHanderCallBack(Handler mH) {
this.mH = mH;
}
@Override
public boolean handleMessage(Message message) {
switch (message.what) {
case LAUNCH_ACTIVITY:
launcherActivity(message);
break;
}
mH.handleMessage(message);
return true;
}
private void launcherActivity(Message message) {
Object obj = message.obj;//ActivityClientRecord
try {
//ActivityClientRecord取出里面的Intent對(duì)象
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyInent = (Intent) intentField.get(obj);
//得到真實(shí)要啟動(dòng)的Activity的Inetnt
Intent realIntent = proxyInent.getParcelableExtra(IActivityManagerHandler.EXTRA_INTENT);
proxyInent.setComponent(realIntent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}
}
static final class ActivityClientRecord {
IBinder token;
int ident;
Intent intent;//這個(gè)實(shí)際就是我們的ProxyActivity對(duì)象對(duì)應(yīng)Intent
String referrer;
IVoiceInteractor voiceInteractor;
Bundle state;
PersistableBundle persistentState;
Activity activity;
Window window;
Activity parent;
String embeddedID;
Activity.NonConfigurationInstances lastNonConfigurationInstances;
boolean paused;
boolean stopped;
boolean hideForNow;
Configuration newConfig;
Configuration createdConfig;
Configuration overrideConfig;
// Used for consolidating configs before sending on to Activity.
private Configuration tmpConfig = new Configuration();
ActivityClientRecord nextIdle;
ProfilerInfo profilerInfo;
.......
}
這樣我們就繞過(guò)了AMS去驗(yàn)證清單文件是否注冊(cè)的問(wèn)題了荧飞。
我們這樣就大功告成了凡人?沒(méi)有運(yùn)行項(xiàng)目:
Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.android.mudl/com.android.mudl.plug.PlugMainActivity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:284)
at android.support.v7.app.AppCompatDelegateImplV7.onCreate(AppCompatDelegateImplV7.java:152)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:46)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:73)
at com.android.mudl.plug.PlugMainActivity.onCreate(PlugMainActivity.java:14)
at android.app.Activity.performCreate(Activity.java:6910)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2746)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2864)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1567)
at com.hickey.mudl.ActivityThreadHanderCallBack.handleMessage(ActivityThreadHanderCallBack.java:31)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6524)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:941)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:831)
從錯(cuò)誤日志看到,我們實(shí)際已經(jīng)偷梁換柱成功叹阔,可是在使用AppCompatActivity時(shí)挠轴,它又去向PackageManger去檢測(cè)父類Activity,沒(méi)找到条获。那怎么辦,我們繼續(xù)hook蒋歌!
//同樣的從ActivityThread入手帅掘,找到sPackageManager,代理它
static IPackageManager sPackageManager;
//具體反射代碼
public static void onHookIPackageManager() {
try {
// 兼容AppCompatActivity報(bào)錯(cuò)問(wèn)題
Class<?> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThread = field.get(null);
Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
Object iPackageManager = getPackageManager.invoke(activityThread);
PackageManagerHandler handler = new PackageManagerHandler(iPackageManager);
Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iPackageManagerIntercept}, handler);
// 獲取 sPackageManager 屬性
Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
iPackageManagerField.setAccessible(true);
iPackageManagerField.set(activityThread, proxy);
}catch (Exception e){
MuL.e("onHookIPackageManager:"+e.toString());
}
這里是找到上面驗(yàn)證失敗的方法getActivityInfo堂油,將里面的ComponentName對(duì)象換成ProxyActivity的修档。
public static class PackageManagerHandler implements InvocationHandler {
public Object iPackageManager;
public PackageManagerHandler(Object iPackageManager) {
this.iPackageManager = iPackageManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getActivityInfo".equals(method.getName())){
for (int i=0;i<args.length;i++){
if (args[i] instanceof ComponentName){
ComponentName componentName = new ComponentName(AppContext.getAppContext().getPackageName(), ProxyActivity.class.getName());
args[i] = componentName;
}
}
}
return method.invoke(iPackageManager,args);
}
}
你會(huì)發(fā)現(xiàn)插件中的使用布局怎么是宿主的布局:
資源加載又成了一個(gè)問(wèn)題
找到ActivityThread
final ArrayMap<String, WeakReference<LoadedApk>> mPackages
= new ArrayMap<String, WeakReference<LoadedApk>>();
替換掉LoadedApk中的mResDir參數(shù):變成我們插件的路徑:
public static void switchToPlugResources(String resPath) {
try {
String packageName = AppContext.getAppContext().getPackageName();
//獲取LoadedApk的Class
Class<?> loadApkCls = Class.forName("android.app.LoadedApk");
//獲取ActivityThread的Class
Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
//獲取ActivityThread對(duì)象
Method currentActivityThreadMethod = activityThreadCls.getMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
//反射獲取mPackages中的LoadedApk
Field filed = activityThreadCls.getDeclaredField("mPackages");
filed.setAccessible(true);
Map mPackages = (Map) filed.get(currentActivityThread);
WeakReference wr = (WeakReference) mPackages.get(packageName);
Field filed2 = loadApkCls.getDeclaredField("mResDir");
filed2.setAccessible(true);
filed2.set(wr.get(), resPath);
}catch (Exception e){
MuL.e("changeResDir:"+e.toString());
}
}
這樣就成功啟動(dòng)插件中的Activity且支持AppCompatActivity.
由于當(dāng)前應(yīng)用的資源路徑變換了,我們需要在適當(dāng)?shù)氖菍①Y源路徑變回來(lái)府框。
我們通過(guò)如上方式重新將資源路徑變換回來(lái),所有方法的調(diào)用順序如下
public class InitRunable implements Runnable {
@Override
public void run() {
MuL.e("Step1:Merge plugins and host Dex.");
String cacheDir = MainActivity.this.getCacheDir().getAbsolutePath();
String apkPath = Environment.getExternalStorageDirectory() + File.separator + "MuPlug.apk";
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, cacheDir, cacheDir, getClassLoader());
DexInject.inject(dexClassLoader);
MuL.e("Step2:Agent IActivityManager Object.");
ActivityManagerHook.onProxyActivityManagerNative();
MuL.e("Step3:Agent ActivityThread mH object.");
ActivityThreadHandlerHook.onProxyActivityThreadmH();
/*MuL.e("Step4:Get ActivityThread sInstrumentation Object.");
ActivityThreadHandlerHook.onProxyActivityInstrumentation(MainActivity.this);*/
/** Switch to the plug-in resource directory */
MuL.e("Step4:Switch to the plug-in resource directory");
LoadApkResDir.switchToPlugResources(apkPath);
IPackageManagerHook.onHookIPackageManager();
runOnUiThread(new Runnable() {
@Override
public void run() {
compatButton.setEnabled(true);
}
});
}
}
//將資源路徑變成我們的apk路徑
LoadApkResDir.switchToPlugResources(getApplicationInfo().sourceDir);
筆記本沒(méi)有電量了吱窝,有點(diǎn)不詳細(xì),請(qǐng)見(jiàn)諒迫靖!