在實際項目中,由於某些業務頻繁變更而導致頻繁升級客戶端的弊病會造成較差的用戶體驗,而這也恰是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
