1. 程式人生 > 其它 >jq 讀取office外掛_外掛化三問—位元組真題

jq 讀取office外掛_外掛化三問—位元組真題

技術標籤:jq 讀取office外掛

提到免安裝應用,大家肯定第一想到的就是小程式,但是在Android中其實是有這麼一項技術用於動態載入apk的,那就是外掛化。今天一起來看看吧!

  • 為什麼需要外掛化
  • 外掛化的原理
  • 市面上的一些外掛化方案以及你的想法

為什麼需要外掛化

我覺得最主要的原因是可以動態擴充套件功能。把一些不常用的功能或者模組做成外掛,就能減少原本的安裝包大小,讓一些功能以外掛的形式在被需要的時候被載入,也就是實現了動態載入

比如動態換膚、節日促銷、見不得人的一些功能,就可以在需要的時候去下載相應模式的apk,然後再動態載入功能。所以一般這個功能適用於一些平臺類的專案,比如大眾點評美團這種,功能很多,使用者很大概率只會用其中的一些功能,而且這些模組單獨拿出來都可以作為一個app執行。

但是現在用的缺很少了,具體情況見第三點。

外掛化的原理

要實現外掛化,也就是實現從apk讀取所有資料,要考慮三個問題:

  • 讀取外掛程式碼,完成外掛中程式碼的載入和與主工程的互相呼叫
  • 讀取外掛資源,完成外掛中資源的載入和與主工程的互相訪問
  • 四大元件管理

1)讀取外掛程式碼,其實也就是進行外掛中的類載入。所以用到類載入器就可以了。Android中常用的有兩種類載入器,DexClassLoaderPathClassLoader,它們都繼承於BaseDexClassLoader。區別在於DexClassLoader多傳了一個optimizedDirectory引數,表示快取我們需要載入的dex檔案的,並建立一個DexFile

物件,而且這個路徑必須為內部儲存路徑。而PathClassLoader這個引數為null,意思就是不會快取到內部儲存空間了,而是直接用原來的檔案路徑載入。所以DexClassLoader功能更為強大,可以載入外部的dex檔案。

同時由於雙親委派機制,在構造外掛的ClassLoader時會傳入主工程的ClassLoader作為父載入器,所以外掛是可以直接可以通過類名引用主工程的類。而主工程呼叫外掛則需要通過DexClassLoader去載入類,然後反射呼叫方法。

2)讀取外掛資源,主要是通過AssetManager進行訪問。具體程式碼如下:

/**
*載入外掛的資源:通過AssetManager新增外掛的APK資源路徑
*/
protectedvoidloadPluginResources(){
//反射載入資源
try{
AssetManagerassetManager=AssetManager.class.newInstance();
MethodaddAssetPath=assetManager.getClass().getMethod("addAssetPath",String.class);
addAssetPath.invoke(assetManager,mDexPath);
mAssetManager=assetManager;
}catch(Exceptione){
e.printStackTrace();
}
ResourcessuperRes=super.getResources();
mResources=newResources(mAssetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
}

通過addAssetPath方法把外掛的路徑穿進去,就可以訪問到外掛的資源了。

3)四大元件管理 為什麼單獨說下四大元件呢?因為四大元件不僅要把他們的類加載出來,還要去管理他們的生命週期,在AndroidManifest.xml中註冊。這也是外掛化中比較重要的一部分。這裡重點說下Activity。

主要實現方法是通過Hook技術,主要的方案就是先用一個在AndroidManifest.xml中註冊的Activity來進行佔坑,用來通過AMS的校驗,接著在合適的時機用外掛Activity替換佔坑的Activity

Hook 技術又叫做鉤子函式,在系統沒有呼叫該函式之前,鉤子程式就先捕獲該訊息,鉤子函式先得到控制權,這時鉤子函式既可以加工處理(改變)該函式的執行行為,還可以強制結束訊息的傳遞。簡單來說,就是把系統的程式拉出來變成我們自己執行程式碼片段。

這裡的hook其實就是我們常說的下鉤子,可以改變函式的內部行為。

這裡載入外掛Activity用到hook技術,有兩個可以hook的點,分別是:

  • Hook IActivityManager 上面說了,首先會在AndroidManifest.xml中註冊的Activity來進行佔坑,然後合適的時機來替換我們要載入的Activity。所以我們主要需要兩步操作:第一步:使用佔坑的這個Activity完成AMS驗證。也就是讓AMS知道我們要啟動的Activity是在xml裡面註冊過的哦。具體程式碼如下:
@Override
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
if("startActivity".contains(method.getName())){
//換掉
Intentintent=null;
intindex=0;
for(inti=0;iObjectarg=args[i];
if(arginstanceofIntent){
//說明找到了startActivity的Intent引數
intent=(Intent)args[i];
//這個意圖是不能被啟動的,因為Acitivity沒有在清單檔案中註冊
index=i;
}
}
//偽造一個代理的Intent,代理Intent啟動的是proxyActivity
IntentproxyIntent=newIntent();
ComponentNamecomponentName=newComponentName(context,proxyActivity);
proxyIntent.setComponent(componentName);
proxyIntent.putExtra("oldIntent",intent);
args[index]=proxyIntent;
}

returnmethod.invoke(iActivityManagerObject,args);
}

第二步:替換回我們的Activity。上面一步是把我們實際要啟動的Activity換成了我們xml裡面註冊的activity來躲過驗證,那麼後續我們就需要把Activity換回來。Activity啟動的最後一步其實是通過H(一個handler)中重寫的handleMessage方法會對LAUNCH_ACTIVITY型別的訊息進行處理,最終會呼叫Activity的onCreate方法。最後會呼叫到Handler的dispatchMessage方法用於處理訊息,如果Handler的Callback型別的mCallback不為null,就會執行mCallback的handleMessage方法。所以我們能hook的點就是這個mCallback


publicstaticvoidhookHandler()throwsException{
Class>activityThreadClass=Class.forName("android.app.ActivityThread");
ObjectcurrentActivityThread=FieldUtil.getField(activityThreadClass,null,"sCurrentActivityThread");//1
FieldmHField=FieldUtil.getField(activityThread,"mH");//2
HandlermH=(Handler)mHField.get(currentActivityThread);//3
FieldUtil.setField(Handler.class,mH,"mCallback",newHCallback(mH));
}

publicclassHCallbackimplementsHandler.Callback{
//...
@Override
publicbooleanhandleMessage(Messagemsg){
if(msg.what==LAUNCH_ACTIVITY){
Objectr=msg.obj;
try{
//得到訊息中的Intent(啟動SubActivity的Intent)
Intentintent=(Intent)FieldUtil.getField(r.getClass(),r,"intent");
//得到此前儲存起來的Intent(啟動TargetActivity的Intent)
Intenttarget=intent.getParcelableExtra(HookHelper.TARGET_INTENT);
//將啟動SubActivity的Intent替換為啟動TargetActivity的Intent
intent.setComponent(target.getComponent());
}catch(Exceptione){
e.printStackTrace();
}
}
mHandler.handleMessage(msg);
returntrue;
}
}

用自定義的HCallback來替換mH中的mCallback即可完成Activity的替換了。

  • Hook Instrumentation

這個方法是由於startActivityForResult方法中呼叫了Instrumentation的execStartActivity方法來啟用Activity的生命週期,所以可以通過替換Instrumentation來完成,然後在InstrumentationexecStartActivity方法中用佔坑SubActivity來通過AMS的驗證,在InstrumentationnewActivity方法中還原TargetActivity。

publicclassInstrumentationProxyextendsInstrumentation{
privateInstrumentationmInstrumentation;
privatePackageManagermPackageManager;
publicInstrumentationProxy(Instrumentationinstrumentation,PackageManagerpackageManager){
mInstrumentation=instrumentation;
mPackageManager=packageManager;
}
publicActivityResultexecStartActivity(
Contextwho,IBindercontextThread,IBindertoken,Activitytarget,
Intentintent,intrequestCode,Bundleoptions){
Listinfos=mPackageManager.queryIntentActivities(intent,PackageManager.MATCH_ALL);if(infos==null||infos.size()==0){
intent.putExtra(HookHelper.TARGET_INTENsT_NAME,intent.getComponent().getClassName());//1
intent.setClassName(who,"com.example.liuwangshu.pluginactivity.StubActivity");//2
}try{
MethodexecMethod=Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);return(ActivityResult)execMethod.invoke(mInstrumentation,who,contextThread,token,
target,intent,requestCode,options);
}catch(Exceptione){
e.printStackTrace();
}returnnull;
}publicActivitynewActivity(ClassLoadercl,StringclassName,Intentintent)throwsInstantiationException,
IllegalAccessException,ClassNotFoundException{
StringintentName=intent.getStringExtra(HookHelper.TARGET_INTENT_NAME);if(!TextUtils.isEmpty(intentName)){returnsuper.newActivity(cl,intentName,intent);
}returnsuper.newActivity(cl,className,intent);
}
}publicstaticvoidhookInstrumentation(Contextcontext)throwsException{
Class>contextImplClass=Class.forName("android.app.ContextImpl");
FieldmMainThreadField=FieldUtil.getField(contextImplClass,"mMainThread");//1
ObjectactivityThread=mMainThreadField.get(context);//2
Class>activityThreadClass=Class.forName("android.app.ActivityThread");
FieldmInstrumentationField=FieldUtil.getField(activityThreadClass,"mInstrumentation");//3
FieldUtil.setField(activityThreadClass,activityThread,"mInstrumentation",newInstrumentationProxy((Instrumentation)mInstrumentationField.get(activityThread),
context.getPackageManager()));
}

市面上的一些外掛化方案以及你的想法

前幾年外掛化還是很火的,比如Dynamic-Load-Apk(任玉剛),DroidPlugin,RePlugin(360),VirtualApk(滴滴),但是現在機會都沒怎麼在運營了,好多框架都只支援到Android9。

這是為什麼呢?我覺得一個是維護成本太高,每更新一次原始碼,就要重新維護一次。二就是確實外掛化技術現在用的不多了,以前用外掛化幹嘛?主要是更新程式碼,修復bug。那麼現在又熱更新技術了,為什麼還還要考慮外掛化呢?元件化+熱更新就完全夠用了。

雖然外掛化用的不多了,但是我覺得技術還是可以瞭解的,而且熱更新主要用的也是這些技術。方案可以被淘汰,但是技術不會。

57ef2d59-131b-eb11-8da9-e4434bdf6706.png

點點在看你最好看

58ef2d59-131b-eb11-8da9-e4434bdf6706.png 59ef2d59-131b-eb11-8da9-e4434bdf6706.png