在實際項目中,因爲某些業務頻繁變動而致使頻繁升級客戶端的弊病會形成較差的用戶體驗,而這也恰是Web App的優點,因而便衍生了一種思路,將核心的易於變動的業務封裝在jar包裏而後經過網絡下載下來,再由android動態加載執行的方案,以改善頻繁升級的毛病html
--前言java
該技術的具體實現步驟可參考農民伯伯的博客:http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html
本文以此爲基礎,擴展了一個簡單的框架,教你們如何使用該技術實現業務的動態變動升級
上效果圖先:android
再看看csdn code上工程的項目結構git
FrameExample 就是咱們的demogithub
frame是給demo引用的連接庫,也就是框架殼api
FrameCore 就是核心框架包,用於被android動態加載的jar包服務器
---------------------------------------------------------------------------------------------------------------------------------
網絡
test目錄展開以下app
hfs2_3b287.rar 一個本地http服務器應用,用於存放測試用的apk和jar包
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會放在上面,這樣你們就不用耗下載積分了,同時便於代碼更正
更多開源項目可關注個人github主頁:https://github.com/geniusgithub