在实际项目中,由于某些业务频繁变更而导致频繁升级客户端的弊病会造成较差的用户体验,而这也恰是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目录展开如下
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