本文并不會告訴你Xposed如何使用,而是解決一個Xposed 每次更新代碼編譯安裝,都要重啟手機才能生效的問題,我們就叫 Xposed 熱更新 問題吧~
Xposed 是什么?如果你不知道,那么請點擊左上角返回按鈕,然后打開瀏覽器,輸入www.baidu.com,搜索關鍵詞”Xposed“~
之前的一篇Xposed破解某短視頻的文章被簡書推薦Xposed 破解某短視頻app(01),好幾天都被點贊和關注的消息”騷擾“,不過那篇文章并沒有講Xposed熱更新的功能(雖然代碼里已經有了),所以這篇文章單獨講一下這個功能。
1:為什么更新Xposed模塊apk,需要重啟手機才能生效?
2:如何做到不重啟能生效?
進入正題:
1、為什么更新Xposed模塊,需要重啟才能生效(原理)
這個問題涉及到Android底層知識和Xposed的原理,這里就簡單按我的理解說一下吧:
1.1 理解Zygote進程
Zygote
進程是Android的核心,所有的應用程序進程以及系統服務進程都是由Zygote進程孵化出來的,所以如果能控制Zygote
進程就等于控制了所有App進程。
Zygote的啟動配置在/init.rc腳本中,手機開機的時候先是啟動init進程,然后啟動Zygote
進程,Zygote進程對應的執行文件是/system/bin/app_process
,這個文件完成類庫加載及一些初始化函數調用的工作
1.2 Xposed的底層原理
Xposed的底層原理是通過替換/system/bin/app_precesss
來控制Zygote進程,并且加載Xposed framework的一個jar文件即 XposedBridge.jar,
這也就解釋了為什么使用Xposed需要root,因為要替換系統文件。
1.3 app_precesss 什么時候被替換?
首次安裝打開Xposed Installer這個App的時候,是需要在里面安裝Xposed版本的,這個安裝過程就是替換/system/bin/app_precesss
的過程,替換之后需要重啟手機才能生效。
以上這些只是大概的原理,具體還需要看源碼才行。
2、如何做到不重啟能生效?
Xposed在手機開機的時候一次性加載和初始化已安裝并勾選的模塊,所以當模塊代碼改變之后,Xposed并不會主動去更新,需要重啟。
反射
Xposed模塊的入口類實現IXposedHookLoadPackage
這個接口,然后重寫handleLoadPackage
方法,這些基礎大家都知道
所以我們可以從 handleLoadPackage
方法入手,加載已經更新的apk的類,然后通過反射調用我們的邏輯代碼即可,具體怎么做呢?
handleLoadPackage 處理
public void handleLoadPackage(final LoadPackageParam param) throws Throwable{
//debug模式下才使用熱更新,因為涉及到反射,影響性能
if (BuildConfig.DEBUG){
debugHook(param);
}else {
releaseHook(param);
}
}
debug 模式下我們需要反射調用我們的入口類,看下 debugHook(param) 具體代碼
debugHook(param)
private void debugHook(final LoadPackageParam param){
final String packageName = Main.class.getPackage().getName();
//查找apk路徑
if (mApkFilePath == null){
Log.d(TAG, "debugHook: mApkFilePath = null");
//1、從 /data/app/com.lanshifu.xposeddemo-1.apk 找
mApkFilePath = String.format("/data/app/%s-%s.apk", packageName, 1);
if (!new File(mApkFilePath).exists()) {
//2、從 /data/app/com.lanshifu.xposeddemo-2.apk 找
mApkFilePath = String.format("/data/app/%s-%s.apk", packageName, 2);
if (!new File(mApkFilePath).exists()) {
//3、從 /data/app/com.lanshifu.xposeddemo-1/base.apk 找
mApkFilePath = String.format("/data/app/%s-%s/base.apk", packageName, 1);
if (!new File(mApkFilePath).exists()) {
//4、從 /data/app/com.lanshifu.xposeddemo-2/base.apk 找
mApkFilePath = String.format("/data/app/%s-%s/base.apk", packageName, 2);
if (!new File(mApkFilePath).exists()) {
LogUtil.e("找不到apk路徑,熱更新失敗:filePath= "+ mApkFilePath + " ,packageName="+packageName);
mApkFilePath = null;
return;
}
}
}
}
}
//通過PathClassLoader 加載apk
final PathClassLoader pathClassLoader = new PathClassLoader(mApkFilePath, ClassLoader.getSystemClassLoader());
String className = MainModule.class.getName();
//通過反射調用 MainModule的 handleLoadPackage 方法【重點】
final Class<?> aClass;
try {
//反射調用MainModule的handleLoadPackage方法
aClass = Class.forName(className, true, pathClassLoader);
final Method aClassMethod = aClass.getMethod("handleLoadPackage", XC_LoadPackage.LoadPackageParam.class);
aClassMethod.invoke(aClass.newInstance(), param);
} catch (Exception e) {
LogUtil.e("反射M ainModule 失敗:"+e.getMessage());
}
}
注釋夠清楚了吧,反射的關鍵一步在于找到apk路徑,我們安裝apk的時候,這個apk會被拷貝到/data/app/packagename*
下面,不同的手機,命名是不一樣的,這里整理了4個不同的路徑:
/data/app/com.lanshifu.xposeddemo-2.apk
/data/app/com.lanshifu.xposeddemo-2.apk
/data/app/com.lanshifu.xposeddemo-1/base.apk
/data/app/com.lanshifu.xposeddemo-2/base.apk
...
其它手機如果熱更新無效,就是apk路徑不對,自己可以通過adb shell,進入/data/app/ 目錄下找對應包名的apk,然后稍微修改下代碼即可。
找到apk路徑之后,通過PathClassLoader
加載這個apk,然后反射調用我們的入口類即可,這里的入口類是MainModule
,入口方法 handleLoadPackage
releaseHook(param)
private void releaseHook(final LoadPackageParam param){
Log.d(TAG, "not hot load - >handleLoadPackage: " + param.packageName);
MainModule module = new MainModule();
module.handleLoadPackage(param);
}
release 模式下直接調用MainModule
的 handleLoadPackage
方法
MainModule
沒啥好說的,就是開始我們的hook代碼
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam param) {
LogUtil.d(" handle packageName = " + param.packageName);
if (param.packageName.equals("com.lanshifu.xposeddemo")) {
hook_method("com.lanshifu.xposeddemo.ui.MainActivity", param.classLoader, "isOpen", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.d("lxb", "hookMainActivity -- >initView");
param.setResult(true);
Toast.makeText((Context) param.thisObject, "模塊已經啟動", Toast.LENGTH_SHORT).show();
}
});
}
//番茄開始
TomatoModule.hookClassLoader(param);
//支付寶偷能量開始
// AliPayModule.handle(param);
}
好了,以上就是Xposed熱更新的全部內容,是不很簡單?
1、找到我們新安裝的apk路徑
2、然后通過反射調用入口方法。
讀了這篇文章,Xposed熱更新學會了嗎?
歡迎點贊+收藏+關注+star
需要源碼?
click hear, show you the code
需要注意一個點:
每次更新代碼,都要先run一次,然后改動下代碼(例如加空行),然后再run,才會顯示模塊已啟動,這個問題我還不知道為什么,反正改了代碼都要run兩次,大家可以自己去試試,看有沒有什么解決辦法。