深入淺出Android動態載入jar包技術
阿新 • • 發佈:2019-01-22
在實際專案中,由於某些業務頻繁變更而導致頻繁升級客戶端的弊病會造成較差的使用者體驗,而這也恰是Web App的優勢,於是便衍生了一種思路,將核心的易於變更的業務封裝在jar包裡然後通過網路下載下來,再由android動態載入執行的方案,以改善頻繁升級的毛病
--前言
該技術的具體實現步驟可參考農民伯伯的部落格:http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html本文以此為基礎,擴充套件了一個簡單的框架,教大家如何使用該技術實現業務的動態變更升級
上效果圖先:
再看看csdn code上工程的專案結構
FrameExample 就是我們的demo
frame是給demo引用的連結庫,也就是框架殼
FrameCore 就是核心框架包,用於被android動態載入的jar包
---------------------------------------------------------------------------------------------------------------------------------test目錄展開如下
des.jar 經過優化的可用於動態載入的jar包
BtcMonitor.apk 測試用的apk
----------------------------------------------------------------------------------------------------------------------
http伺服器介面如下:
demo通過frame庫提供的api介面呼叫到framecore核心包裡的具體實現程式碼
這樣當業務具體實現有變更時,只需修改framecore裡的內容,然後放到網路上
framecore裡可以實現一個自檢測jar包版本並自動更新下載jar包的功能,本例去掉了這個功能
有興趣的童鞋可以自己嘗試一下,另外本例只提供了下載的介面,其它介面根據框架模板定義自行新增即可
再貼一個核心類FrameInstance的實現:
public class FrameInstance implements IFrame{ private static final CommonLog log = LogFactory.createLog(); private static FrameInstance mInstance; private boolean isFrameInit = false; private Context mContext; private Handler mJarHandler; private DownloadJARProxy mJARProxy; private ISimpleDownloadCallback mJARDownloadCallback; public static synchronized FrameInstance getInstance(Context context) { if (mInstance == null){ mInstance = new FrameInstance(context); } return mInstance; } private FrameInstance(Context context) { mContext = context; mJarHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case IHandlerMsg.ON_START: break; case IHandlerMsg.ON_PROGRESS: { int cur = msg.arg1; int max = msg.arg2; double rate = cur * 1.0 / max; int value = (int) (rate * 100); String conString = String.valueOf(value) + "%"; log.e("download jar percent:" + conString); } break; case IHandlerMsg.ON_SUCCESS: if (mJARDownloadCallback != null){ mJARDownloadCallback.onDownload(true); } break; case IHandlerMsg.ON_FAIL: if (mJARDownloadCallback != null){ mJARDownloadCallback.onDownload(false); } break; case IHandlerMsg.ON_CANCEL: break; } } }; } @Override public boolean startFramework() { if (isFrameInit){ return true; } isFrameInit = loadFrameCore(); log.e("startFramework ret = " + isFrameInit); if (mIFrameCore != null){ mIFrameCore.startEngine(mContext); } return isFrameInit; } @Override public boolean stopFramework() { if (!isFrameInit){ return true; } if (mIFrameCore != null){ mIFrameCore.stopEngine(mContext); } releaseFrameCore(); isFrameInit = false; log.e("stopFramework... "); return true; } @Override public boolean isFrameworkInit() { return isFrameInit; } @Override public boolean isFrameCoreExist() { if (!FrameTool.hasSDCard()) return false; File file = new File(FrameTool.getJARSavePath()); if (!file.exists()){ return false; } return true; } @Override public boolean startDownloadFrameCore(ISimpleDownloadCallback callback) { if (!FrameTool.hasSDCard()){ log.e("SDCard not exist!!!"); return false; } if (mJARProxy != null){ if (mJARProxy.isTaskRunning()){ return true; } } mJARProxy = new DownloadJARProxy(mJarHandler); mJARProxy.startDownloadTask(FrameTool.getJARURL(), FrameTool.getJARSavePath()); mJARDownloadCallback = callback; log.e("startDownloadFrameCore...JAR_URL:" + FrameTool.getJARURL() + ", SAVE_PATH:" + FrameTool.getJARSavePath()); return true; } public boolean updateDownloadAPK(String url, String dstPath, IUpdateDownloadCallback callback){ if (mIUpdateTools == null){ log.e("mIUpdateTools = null!!!"); return false; } if (TextUtils.isEmpty(url) || TextUtils.isEmpty(dstPath)){ return false; } mIUpdateTools.updateDownloadAPK(url, dstPath, callback); return true; } public boolean cancelDownloadAPK(){ if (mIUpdateTools == null){ log.e("mIUpdateTools = null!!!"); return false; } mIUpdateTools.cancelDownloadAPK(); return true; } public boolean checkJARVersion(){ return true; } public boolean installAPK(String path){ if (TextUtils.isEmpty(path)){ return false; } Intent i = new Intent(Intent.ACTION_VIEW); i.setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive"); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(i); return true; } private IFrameCore mIFrameCore; private IUpdateTools mIUpdateTools; private boolean loadFrameCore(){ // if (true){ // mIVersionTools = new VersionTools(); // mIUpdateTools = new UpdateTools(); // return true; // } if (!isFrameCoreExist()){ return false; } DexClassLoader classLoader = DexClassLoadTools.newDexClassLoader(mContext, FrameTool.getJARSavePath()); if (classLoader == null){ return false; } mIFrameCore = ClassFactory.newFrameCore(classLoader); if (mIFrameCore == null){ return false; } mIUpdateTools = ClassFactory.newUpdateTools(classLoader); return true; } private void releaseFrameCore(){ mIFrameCore = null; mIUpdateTools = null; } private static class ClassFactory{ public static IFrameCore newFrameCore(DexClassLoader classLoader){ IFrameCore object = null; Class cls = newFrameCoreClass(classLoader, "com.lance.framecore.externex.FrameCore"); if (cls != null){ try { object = (IFrameCore) cls.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return object; } public static IUpdateTools newUpdateTools(DexClassLoader classLoader){ IUpdateTools object = null; Class cls = newFrameCoreClass(classLoader, "com.lance.framecore.externex.UpdateTools"); if (cls != null){ try { object = (IUpdateTools) cls.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return object; } public static Class newFrameCoreClass(DexClassLoader classLoader, String className){ Class libProviderClazz = null; try { libProviderClazz = classLoader.loadClass(className); } catch (Exception exception) { exception.printStackTrace(); } return libProviderClazz; } } public static class FrameTool{ private final static String JAR_URL = "http://192.168.1.101/jar/des.jar"; public static boolean hasSDCard() { String status = Environment.getExternalStorageState(); if (!status.equals(Environment.MEDIA_MOUNTED)) { return false; } return true; } public static String getRootFilePath() { if (hasSDCard()) { return Environment.getExternalStorageDirectory().getAbsolutePath() + "/"; } else { return Environment.getDataDirectory().getAbsolutePath() + "/data/"; } } public static String getJARURL(){ return JAR_URL; } public static String getJARSavePath(){ return getRootFilePath() + "FrameCore.jar"; } } private static class DexClassLoadTools{ public static DexClassLoader newDexClassLoader(Context context, String jarPath){ final File optimizedDexOutputPath = new File(jarPath); if (!optimizedDexOutputPath.exists()){ return null; } File file = context.getDir("osdk", 0); log.e("getDir:" + file.getAbsolutePath()); DexClassLoader cl = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), file.getAbsolutePath(), null, context.getClassLoader()); return cl; } } @Override public boolean deleteFrameCore() { log.e("deleteFrameCore"); if (!isFrameCoreExist()){ log.e("framecore.jar is not exist:" + FrameTool.getJARSavePath()); return false; } FileTools.deleteDirectory(FrameTool.getJARSavePath()); return true; } @Override public FrameCoreInfo getFrameCoreInfo() { try { if (mIFrameCore == null){ return new FrameCoreInfo(); } return mIFrameCore.getFrameCoreInfo(mContext); } catch (Exception e) { e.printStackTrace(); return new FrameCoreInfo(); } } }
值得注意的是
在匯出framecore時無需匯出com.lance.framecore.extern包下程式碼,否則載入時會出現重複定義錯誤
同時要保持framecore工程和frame工程該包程式碼的一致,在擴充套件介面時把相應介面寫在這個包下即可
--------------------------------------------------------------------------------------------------------------------------------
其它的沒啥好說的了,自個兒download程式碼看吧下面附上code地址:https://code.csdn.net/geniuseoe2012/dynamicjar
如果童鞋們覺得本文有用,不妨關注我的code主頁:https://code.csdn.net/geniuseoe2012
以後一些博文相關的demo會放在上面,這樣大家就不用耗下載積分了,同時便於程式碼更正