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

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


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


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


动态图:android 动态加载之免安装升级(插件式开发)_第1张图片


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

  • 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包。请看图:

android 动态加载之免安装升级(插件式开发)_第2张图片
android 动态加载之免安装升级(插件式开发)_第3张图片
android 动态加载之免安装升级(插件式开发)_第4张图片


我们导出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

你可能感兴趣的:(进阶)