android 动态加载之免安装升级(插件式开发)

来源:互联网 发布:传奇3怪物数据库 编辑:程序博客网 时间:2024/06/07 01:43

{大半年没更新博客,一开始是忙坏了,后面没忙了发现自己不想写了,变懒惰,今天重新开始检讨自己,并且要坚持写博客这个习惯。一定要坚持下来分享自己的技术心德。}


今天我们就说讨论讨论,[动态加载:]顾名思义,就是让apk不需要升级就能更新的功能,也可以理解为”插件”,也可以叫插件式开发。


动态加载有几种比如说有加载apk的,加载dex的jar的等等,这里我们主要针对加载dex的jar这种形式。


动态图:这里写图片描述


现在咱们说说如何实现这个功能呢。

  • 1、其实呢就跟你项目里面集成了一个jar一个概念,只是这个jar不在项目里面了,而是在sdcard或者别的地方。这样就能完成需要升级的时候,去下载最新的jar并加载,从而达到不需要升级apk就能更新的功能。

  • 2、这里我们主要项目为一个主体项目,一个jar项目。具体实现咱们在后面边看图边讲解。

  • 3、jar项目打包出来的项目,需要由.class文件转成dex文件的jar,这里需要用到一个dx的工具,后面也会依次介绍,当然了网上也有下,我这里也会提供。废话不多说,咱们现在就进入高潮!!!


首先是启动类代码如下(这里呢所有触发都在点击事件里面,由于没设立服务器,我们暂时把需要动态加载的jar包放在assets文件夹里面,然后在拷贝进sdcard里面去,去加载sdcard里面的jar。拷贝成功之后就该去加载dex,并且启动里面的start方法。该方法后面会介绍干啥用):

package com.test.demo;import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.os.Environment;import android.view.View;import android.widget.Button;import com.example.test.R;/** * create by wangyuwen at 2017/4/11. */public class MainActivity extends Activity implements View.OnClickListener {    private Button btn_jar;    //sdcar里面存储dex    public static final String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "dynamic_V1.jar";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn_jar = (Button) findViewById(R.id.btn_jar);        btn_jar.setOnClickListener(this);    }    @Override    public void onClick(View view) {        if (view == btn_jar) {// 加载第一个dex            File file = new File(filePath);            if (!file.exists()) {                AssetsCopySystemCard(this, "dynamic_V1.jar", filePath);            }            // 因为所有类都反射到同一个activity,所以需要把唯一的承载跳转activity的intent传进去            Intent intent = new Intent();            intent.setClassName(getPackageName(), "com.test.demo.IntentActivity");            // 加载动态dex里面的唯一通道,拿到dex里面的类对象,并且开始调用start方法            DynamicUtil.DynamicAc(this, null).ExecuteMethod("start", new Class[] { Intent.class }, new Object[] { intent });        }    }    /**     * 把Assets里面的文件拷贝到sdcard     *      * @return     */    public static synchronized void AssetsCopySystemCard(Context context, String assets, String path) {        InputStream is = null;        FileOutputStream fos = null;        try {            File file = new File(path);            if (!file.exists()) {                file.createNewFile();            }            is = context.getAssets().open(assets);            // 第二个参数是是否追加,false是覆盖,true是追加            fos = new FileOutputStream(file, false);            byte[] buffer = new byte[1024];            int byteCount = 0;            while ((byteCount = is.read(buffer)) != -1) {// 循环从输入流读取 buffer字节                fos.write(buffer, 0, byteCount);// 将读取的输入流写入到输出流            }        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (is != null) {                    is.close();                }                if (fos != null) {                    fos.close();                }            } catch (Exception e) {                e.printStackTrace();            }        }    }}

因为有界面需要跳转,所以我们需要一个承载的activity代码如下(这个类啥都没干,就是去加载dex里面的activity的基类。因为在启动类里面有个传了包含这个承载类的intent进去,所以跳转之后就会到这里来。):

package com.test.demo;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.util.Log;/** * Created by wyw on 2017/4/11. */public class IntentActivity extends Activity {    public ObjectAcUtil objectAcUtil;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        try {            //加载动态dex,拿到dex里面的反射类对象            objectAcUtil = DynamicUtil.DynamicAc(this, "Activity");            objectAcUtil.ExecuteMethod("onCreate", new Class[] { Bundle.class }, new Object[] { savedInstanceState });        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onCreate Activity 失败", e);        }    }    @Override    protected void onStart() {        super.onStart();        try {            objectAcUtil.ExecuteMethod("onStart", null, null);        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onStart Activity 失败", e);        }    }    @Override    protected void onResume() {        super.onResume();        try {            objectAcUtil.ExecuteMethod("onResume", null, null);        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onResume Activity 失败", e);        }    }    @Override    protected void onRestart() {        super.onRestart();        try {            objectAcUtil.ExecuteMethod("onRestart", null, null);        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onRestart Activity 失败", e);        }    }    @Override    protected void onPause() {        super.onPause();        try {            objectAcUtil.ExecuteMethod("onPause", null, null);        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onPause Activity 失败", e);        }    }    @Override    protected void onStop() {        super.onStop();        try {            objectAcUtil.ExecuteMethod("onStop", null, null);        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onStop Activity 失败", e);        }    }    @Override    protected void onDestroy() {        super.onDestroy();        try {            objectAcUtil.ExecuteMethod("onDestroy", null, null);        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onDestroy Activity 失败", e);        }    }    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        try {            objectAcUtil.ExecuteMethod("onActivityResult", null, null);        } catch (Exception e) {            Log.e(getPackageName(), "[载体]onActivityResult Activity 失败", e);        }    }}

接下来就是动态加载的核心代码了(这里是2个类,一个加载dex的工具类,一个是反射过来的object类,来加载该类里面的方法):

package com.test.demo;import java.io.File;import android.annotation.SuppressLint;import android.app.Activity;import android.text.TextUtils;import android.util.Log;import dalvik.system.DexClassLoader;/** * create by wangyuwen at 2017/4/11. */@SuppressLint("NewApi")public class DynamicUtil {    /* 反射的类名 */    public static final String CLASSNAME = "com.dynamic.Dynamic";    /* 反射的类名activity */    public static final String CLASSNAME_AC = "com.dynamic.DynamicAC";    public static ObjectAcUtil DynamicAc(Activity activity, String strAc) {        try {            File jarFile = new File(MainActivity.filePath);            if (jarFile.exists()) {                DexClassLoader dexCl = new DexClassLoader(MainActivity.filePath, activity.getCacheDir().getAbsolutePath(), null,                        DynamicUtil.class.getClassLoader());                Class acl;                if (TextUtils.isEmpty(strAc)) {                    acl = dexCl.loadClass(DynamicUtil.CLASSNAME);                } else {                    acl = dexCl.loadClass(DynamicUtil.CLASSNAME_AC);                }                return new ObjectAcUtil(activity, acl);            }        } catch (Throwable e) {            Log.e(activity.getPackageName(), "[动态加载]  出错!", e);        }        return null;    }}package com.test.demo;import java.lang.reflect.Constructor;import android.app.Activity;import android.util.Log;public class ObjectAcUtil {    /* 反射出来的对象(这个就等于某个类被new出来的对象) */    private Object object;    public ObjectAcUtil(Activity activity, Class c) {        try {            Constructor constructor = c.getConstructor(new Class[] { Activity.class });            object = constructor.newInstance(new Object[] { activity });        } catch (Exception e) {            Log.e("123456", "ObjectUtil Service", e);        }    }    public synchronized Object ExecuteMethod(String methodName, Class[] parameterType, Object[] parameter) {        try {            if (parameterType == null && parameter == null) {                return object.getClass().getMethod(methodName).invoke(object);            } else {                return object.getClass().getMethod(methodName, parameterType).invoke(object, parameter);            }        } catch (Exception e) {            Log.e("123456", "ExecuteMethod", e);        }        return null;    }}

主体项目总工就只有4个类,Manifest文件我就不贴了,里面就注册一个启动activity和承载activity就行了。接下来看下被加载项目,也就是jar项目。


首先看下上面启动类点击事件里面调用的类到底做了些什么呢?代码如下(这里贴的是2个类,第一个类呢也就是点击事件里面调用的类,并且反射了start方法,start方法就只干了一件事,就是加个bundle然后跳转activity,因为intent是传下来的,所以不用指定跳转activity,这里需要把你写界面的那个类的类名传进去为什么要传呢, 后面会介绍。下面那个类是跳转工具类,我也直接贴在这里了,跳转工具类里面的参数不懂的自行百度,这里就不做过多描述。):

package com.dynamic;import android.app.Activity;import android.content.Intent;import android.os.Bundle;/** * create by wangyuwen at 2017/4/11 0011 */public class Dynamic {    private Activity activity;    public Dynamic(Activity activity) {        this.activity = activity;    }    public void start(Intent intent) {        startIntentView(intent);    }    public void startIntentView(Intent intent) {        Bundle bundle = new Bundle();        bundle.putString("String", "d动态加载的JAR");        HelpUtil.IntentView(activity, intent, ViewB.class.getName(), bundle);    }}package com.dynamic;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.util.Log;/** * create by wangyuwen at 2017/4/12 0012 */public class HelpUtil {    public static final String KEY_VIEW_NAME = "key_view_name";    public static void IntentView(Context context, Intent intent, String className, Bundle data) {        if (intent != null) {            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);            intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);            intent.putExtra(HelpUtil.KEY_VIEW_NAME, className);            intent.putExtras(data);            context.startActivity(intent);            Log.i("123456", "start intent activity!");        } else {            Log.e("123456", "intent = null");        }    }}

跳转到了承载activity,上面也介绍了承载activity只干一件事,就是反射dex里面的activity基类,也就是即将要介绍的请看代码(这里就会有很多要问我了,为什么这里还要去反射,因为需求是主体项目在不需要更新的情况去更新jar项目,这就导致主体项目的承载activity只能反射到这里,要想灵活开发的话,这里肯定要去反射一个ui基类,ui实现类去继承这个ui基类,因为你会写很多ui实现类去跳转所以需要基类去定位调用。反射类名就在bundle里面,上面就说到bundle里面包含了你写的ui实现类的类名进来):

package com.dynamic;import java.lang.reflect.Constructor;import android.app.Activity;import android.content.Intent;import android.os.Bundle;/** * create by wangyuwen at 2017/4/11 0011 */public class DynamicAC {    private Activity activity;    private BaseView baseView;    public DynamicAC(Activity activity) {        this.activity = activity;    }    public void onCreate(Bundle savedInstanceState) {        try {            String className = activity.getIntent().getStringExtra(HelpUtil.KEY_VIEW_NAME);            Bundle data = activity.getIntent().getExtras();            Class c = Class.forName(className);            Class[] paramTypes = { Activity.class, Bundle.class };            Object[] params = { activity, data };            Constructor constructor = c.getConstructor(paramTypes);            baseView = (BaseView) constructor.newInstance(params);            baseView.onCreate(savedInstanceState);        } catch (Exception e) {            activity.finish();        }    }    public void onStart() {        try {            baseView.onStart();        } catch (Exception e) {            activity.finish();        }    }    public void onResume() {        try {            baseView.onResume();        } catch (Exception e) {            activity.finish();        }    }    public void onRestart() {        try {            baseView.onRestart();        } catch (Exception e) {            activity.finish();        }    }    public void onPause() {        try {            baseView.onPause();        } catch (Exception e) {            activity.finish();        }    }    public void onStop() {        try {            baseView.onStop();        } catch (Exception e) {            activity.finish();        }    }    public void onDestroy() {        try {            baseView.onDestroy();        } catch (Exception e) {            activity.finish();        }    }    public void onActivityResult(int requestCode, int resultCode, Intent data) {        try {            baseView.onActivityResult(requestCode, resultCode, data);        } catch (Exception e) {            activity.finish();        }    }}

接下来我们看看ui实现类和基类了,代码如下(第一个是基类,很清楚明了构造方法里面拿到bundle和activity,然后写一些抽象方法,让继承的人去实现。第二个是实现类,构造里面的super下面去拿bundle里面String去放进textview里面去,onCreate就是干创建视图的工作了,注意了jar项目里面不能用res文件,所以所有视图得用代码去写,所以这就造成了要对代码写ui的要求很高。):

package com.dynamic;import android.app.Activity;import android.content.Intent;import android.os.Bundle;/** * create by wangyuwen at 2017/4/11 0011 */public abstract class BaseView {    protected Bundle bundle;    protected Activity activity;    public BaseView(Activity activity, Bundle bundle) {        this.activity = activity;        this.bundle = bundle;    }    public abstract void onCreate(Bundle savedInstanceState);    public abstract void onStart();    public abstract void onResume();    public abstract void onRestart();    public abstract void onPause();    public abstract void onStop();    public abstract void onDestroy();    public abstract void onActivityResult(int requestCode, int resultCode, Intent data);}package com.dynamic;import android.app.Activity;import android.content.Intent;import android.graphics.Color;import android.os.Bundle;import android.util.Log;import android.view.Gravity;import android.view.ViewGroup;import android.widget.LinearLayout;import android.widget.TextView;/** * create by wangyuwen at 2017/4/11 0011 */public class ViewB extends BaseView {    private String str;    public ViewB(Activity activity, Bundle bundle) {        super(activity, bundle);        str = bundle.getString("String");    }    @Override    public void onCreate(Bundle savedInstanceState) {        Log.i("123456", "进来了!");        LinearLayout linearLayout = new LinearLayout(activity);        linearLayout.setOrientation(LinearLayout.VERTICAL);        linearLayout.setGravity(Gravity.CENTER);        linearLayout.setBackgroundColor(Color.BLACK);        linearLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));        TextView textView = new TextView(activity);        textView.setText(str + "");        textView.setTextColor(Color.WHITE);        textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));        linearLayout.addView(textView);        activity.setContentView(linearLayout);        Log.i("123456", "执行完了!onCreate");    }    @Override    public void onStart() {        Log.i("123456", "onStart!");    }    @Override    public void onResume() {        Log.i("123456", "onResume!");    }    @Override    public void onRestart() {        Log.i("123456", "onRestart!");    }    @Override    public void onPause() {        Log.i("123456", "onPause!");    }    @Override    public void onStop() {        Log.i("123456", "onStop!");    }    @Override    public void onDestroy() {        Log.i("123456", "onDestroy!");    }    @Override    public void onActivityResult(int requestCode, int resultCode, Intent data) {        Log.i("123456", "onActivityResult!");    }}

到了这里项目差不多就介绍完了,现在可以说如何打包成dex文件。第一步肯定是jar项目导jar包。请看图:

第一步
第二步
第三步


我们导出jar之后,现在是要转dex文件了。这里我们会需要用到一个dx工具,这个工具我会一起放入项目的zip里面。

然后用法如下将dx.zip解压,将其里面的资源拷贝到android sdk platform-tools目录下即可使用(window环境)。

编译命令,cmd进入到android sdk platform-tools目录 dx –dex –output=target.jar origin.jar

上述命令中 origin.jar为源代码导出的jar包(源码包要在android sdk platform-tools目录里面),target.jar为dx工具产生的dex二进制jar包!

生成的target.jar文件就是已经转好的文件,这时候就可以把这个文件丢入主体项目的aseets目录里面了,然后开始跑动你的项目试试吧。

做到这一步就赶紧把你的代码运行起来吧!!本篇博客就到这里,如果有有疑问的欢迎留言讨论。同时希望大家多多关注我的博客,多多支持我。

尊重原创转载请注明:(http://blog.csdn.net/u013895206) !

下面是地址传送门:址:http://download.csdn.net/detail/u013895206/9837770

0 0