摘要:也是較容易出現(xiàn)問題的一個模塊,對層實(shí)現(xiàn)熱修復(fù),對項(xiàng)目的穩(wěn)定性是十分有利的。需求在不修改層和層的基礎(chǔ)上,實(shí)現(xiàn)對層的熱修復(fù)。通過不停的更換所加載的文件,但調(diào)用方法一致,來達(dá)到熱修復(fù)的目的。綜上,本方案應(yīng)該是可以在項(xiàng)目中實(shí)際應(yīng)用的一個熱修復(fù)方案。
本文為作者原創(chuàng),轉(zhuǎn)載請注明出處由來
現(xiàn)在開發(fā)android項(xiàng)目大部分都已經(jīng)由mvc轉(zhuǎn)移到了mvp,關(guān)于mvp是什么大致也不必多說了,無非三個層:
m:model層,一般封裝對數(shù)據(jù)的操作,增刪改查,接口訪問等等。
v:view層,也就是視圖層,視圖層不主動做什么,只是根據(jù)某些事件作出對應(yīng)的視圖展示。
p:Presenter層,也就是邏輯層。
圖解(之前在別的博文看到的,覺得比較好就直接拿來用了):
在mvp模式中,model層和view層不再有直接交互,而把相應(yīng)的工作交給了“中間人”Presenter層來處理。
因此在我們的項(xiàng)目中,Presenter層可以說是一個改動比較頻繁,邏輯比較復(fù)雜的一個模塊。也是較容易出現(xiàn)問題的一個模塊,對Presenter層實(shí)現(xiàn)熱修復(fù),對項(xiàng)目的穩(wěn)定性是十分有利的。
總結(jié)一下:
項(xiàng)目背景:mvp的項(xiàng)目。
需求:在不修改model層和view層的基礎(chǔ)上,實(shí)現(xiàn)對Presenter層的熱修復(fù)。
構(gòu)想核心思想就是Presenter層只寫接口,然后使用java的classloader機(jī)制加載Presenter層的實(shí)現(xiàn)類來產(chǎn)生對象然后賦值給接口指針調(diào)用。通過不停的更換classloader所加載的文件,但調(diào)用方法一致,來達(dá)到熱修復(fù)的目的。
下面是我畫的一個整體的結(jié)構(gòu)圖。
既然大體思路都有了,那么咱們就來嘗試一下能不能行得通吧。做一個案例,功能非常簡單,界面上一個按鈕,點(diǎn)擊按鈕吐司一個字符串,這個字符串通過。
1.創(chuàng)建項(xiàng)目除了一路next之外我這里想說的實(shí)際上是項(xiàng)目module結(jié)構(gòu):
app里面主要寫項(xiàng)目的相關(guān)界面。
motorlib里面主要寫presenter接口和熱修復(fù)、classloadler等相關(guān)的代碼,另外model層也可以在里面寫,其實(shí)它們可以寫在app里面,但這樣組件化的話,更有利于解耦。
motorhot這里就是寫presenter的具體實(shí)現(xiàn)了,另外model層也可以在里面寫,那樣的話對于后臺接口返回的數(shù)據(jù)格式變更等也可以進(jìn)行修復(fù)了。
再設(shè)置一下這三個module之間的引用關(guān)系。
.appbuild.gradle
dependencies { ... //只關(guān)心接口,不關(guān)心實(shí)現(xiàn),因此只引用motorlib implementation project(":motorlib") }
.motorhotbuild.gradle
dependencies { ... //需要集成motorlib定義的接口,因此需要引用 implementation project(":motorlib") }
.motorlibbuild.gradle
dependencies { ... //因?yàn)橹欢x接口,所以都不需要引用 }2.persenter接口
在motorlib中創(chuàng)建一個java接口,AppParsenter,里面只有一個方法:
public interface AppParsenter { String getStr(); }3.實(shí)現(xiàn)接口persenter接口
在motorlib中創(chuàng)建一個java類,實(shí)現(xiàn)AppParsenter:
public class AppParsenterImpl implements AppParsenter { @Override public String getStr() { return "hello word !"; } }4.熱修復(fù)文件打包
在android studio中的右側(cè),打開Gradle一欄,然后點(diǎn)擊(也可以直接運(yùn)行g(shù)radlew命令gradlew motorhot:assembleRelease):
打包后的jar包文件存放在.motorhotuildintermediatesundles
eleaseclasses.jar
拿到打包好的classes.jar,然后使用android sdk提供的dx.bat將jar包轉(zhuǎn)換為dex:
CD ..androidsdkuild-tools20.0.0 dx --dex --output=..hotfix.dex ..classes.jar
這樣,用于熱修復(fù)的.dex文件就打包完成了。
5.ObjectFactory因?yàn)閐ex的加載離不開DexClassLoader,因此我在這里先對DexClassLoader進(jìn)行了一下封裝:
public class Motor { private Context context; private MotorListener listener; private static volatile Motor motor; private DexClassLoader mClassLoader; private Motor() { } public static Motor get() { if (motor == null) { synchronized (Motor.class) { if (motor == null) { motor = new Motor(); } } } return motor; } public static void init(Context context, MotorListener listener) { get(); motor.context = context; motor.listener = listener; //加載dex motor.initClassLoader(); listener.initFnish(); //todo 網(wǎng)絡(luò)檢查dex更新 } private void initClassLoader() { String dexDir = context.getCacheDir().getAbsolutePath() + "/dex/"; File dir = new File(dexDir); if (!dir.exists()) { dir.mkdirs(); } File dexFile = new File(dir, "mydex.dex"); if (!(dexFile.exists() && dexFile.isFile() && dexFile.length() > 0)) { copyFileFromAssets(context, "mydex.dex", dexFile.getAbsolutePath()); } mClassLoader = new DexClassLoader( dexFile.getAbsolutePath(), context.getFilesDir().getAbsolutePath() , null, context.getClassLoader()); } public boolean copyFileFromAssets(Context context, String assetName, String path) { boolean bRet = false; try { InputStream is = context.getAssets().open(assetName); File file = new File(path); file.createNewFile(); FileOutputStream fos = new FileOutputStream(file); byte[] temp = new byte[64]; int i = 0; while ((i = is.read(temp)) > 0) { fos.write(temp, 0, i); } fos.close(); is.close(); bRet = true; } catch (IOException e) { e.printStackTrace(); } return bRet; } public DexClassLoader getClassLoader() { return mClassLoader; } public void setmClassLoader(DexClassLoader mClassLoader) { this.mClassLoader = mClassLoader; } public Context getContext() { return context; } public void setContext(Context context) { this.context = context; } public interface MotorListener { void initFnish(); void initError(Throwable throwable); } }
單例,首先從assets中把.dex文件拷貝到android的沙盒目錄(android加載dex的時候有限制,必須是在沙盒目錄中才能加載),然后構(gòu)建好了mClassLoader就完成了。
方便起見,熱修復(fù)文件就不從網(wǎng)絡(luò)下載了,因此直接把打包好的.dex拷貝到項(xiàng)目的assets文件夾中。
在motorlib中創(chuàng)建ObjectFactory,用來通過classloader生產(chǎn)對象:
public class ObjectFactory { public static Object make(String type) { try { Classap = (Class ) Motor.get() .getClassLoader().loadClass("com.example.motordex.AppParsenterImpl"); AppParsenter o = ap.newInstance(); return o; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } return null; } }
到了這里就可以注意到了,反射構(gòu)建對象的時候ap.newInstance();沒法傳參數(shù),因此對于Parsenter的實(shí)現(xiàn)類中,必須有一個無參的構(gòu)造方法。
6.運(yùn)行app中新建一個activity,設(shè)置一個按鈕然后添加點(diǎn)擊事件:
layout/activity_main.xml
com.example.miqt.dexmvppdemo.MainActivity
public class MainActivity extends AppCompatActivity { AppParsenter ap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Motor.init(this, new Motor.MotorListener() { @Override public void initFnish() { ap = (AppParsenter) ObjectFactory.make(MainActivity.class.getName()); } @Override public void initError(Throwable throwable) { } }); } public void getStr(View view) { String str = ap.getStr(); Toast.makeText(this, str, Toast.LENGTH_SHORT).show(); } }
運(yùn)行結(jié)果:
修改motorhot中的AppParsenterImpl,模擬修復(fù)了一個bug:
public class AppParsenterImpl implements AppParsenter { @Override public String getStr() { return "fix a bug!"; } }
重復(fù)4步驟,打包運(yùn)行:
事實(shí)證明這種構(gòu)想完全可以實(shí)現(xiàn),并且可以在不使用其他框架,達(dá)到熱修復(fù)的目的,并且不僅僅是presenter層,在其他的層應(yīng)用這種方式,也是可以的。
但實(shí)際上從上面的實(shí)踐,也可以發(fā)現(xiàn)一些問題:
dex文件線上下載或拷貝過程中如果損壞,則可能引起程序崩潰。不過我們可以使用對dex文件取hash碼然后對比下載后的文件,如果不一致則證明出錯,重新下載拷貝。解決這個問題。
dex暴露在用戶手機(jī)沙盒目錄中,而android的沙盒目錄在root之后是可以直接訪問的,因此有可能被人拿到dex反編譯,修改邏輯搞破壞。并且因?yàn)榉瓷涞挠绊?,motorhot中的類文件是不可以進(jìn)行混淆的,因?yàn)榛煜缶蜁笳也坏筋惖漠惓?。不過關(guān)于這個也是有解決辦法的,我想到的那就是對熱修復(fù)文件加密,在loader之前再在內(nèi)存中解密。這樣別人沒法知道加密規(guī)則,也就沒法解密了。
雙親委托機(jī)制,在classloader加載外部dex之前會先檢查本地是否已經(jīng)存在同名的類,如果有則優(yōu)先加載本地已經(jīng)存在的類,因此在實(shí)際使用中我們最好還要禁用雙親委托機(jī)制。
綜上,本方案應(yīng)該是可以在項(xiàng)目中實(shí)際應(yīng)用的一個熱修復(fù)方案。
完整代碼:https://github.com/miqt/MVPHo...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/76756.html
摘要:理解內(nèi)存模型對多線程編程無疑是有好處的。干貨高級動畫高級動畫進(jìn)階,矢量動畫。 這是最好的Android相關(guān)原創(chuàng)知識體系(100+篇) 知識體系從2016年開始構(gòu)建,所有的文章都是圍繞著這個知識體系來寫,目前共收入了100多篇原創(chuàng)文章,其中有一部分未收入的文章在我的新書《Android進(jìn)階之光》中。最重要的是,這個知識體系仍舊在成長中。 Android 下拉刷新庫,這一個就夠了! 新鮮出...
摘要:當(dāng)然,目前看來,的勢頭是蓋過的。平臺的插件化框架也是存在多種方案,各有優(yōu)劣。常見的攜程的,的,的以及等。另外,插件化也是解決問題的一大利器。 在 2016 年學(xué) Android 是一種什么樣的體驗(yàn)? @author ASCE1885的 Github 簡書 微博 CSDN 知乎本文由于潛在的商業(yè)目的,不開放全文轉(zhuǎn)載許可,謝謝! showImg(/img/remote/146000000...
閱讀 1714·2021-08-30 09:45
閱讀 1763·2019-08-30 15:54
閱讀 1185·2019-08-30 14:02
閱讀 1945·2019-08-29 16:21
閱讀 1622·2019-08-29 13:47
閱讀 3205·2019-08-29 12:27
閱讀 707·2019-08-29 11:01
閱讀 2673·2019-08-26 14:04