• Android动态加载字节码


    概述

    面对App业务逻辑的频繁变更,如果每一次改变都对App进行一次升级,会降低App的用户体验,那么App进行模块化升级(这里与增量升级是不同的)是很好的解决方案,让用户在完全无感觉的情况下改变App中的业务逻辑。要实现这种模块化升级,动态加载字节码(jar/dex)就是实现这个需求的理论基础。

    Android系统加载字节码

    Android的虚拟机(Dalvik VM)无法识别普通jar包中的字节码,所以需要通过字节码转换工具将jar转换成dex,jar包中的所有字节码都会打进classes.dex,这样的字节码才能被Dalvik虚拟机识别。

    实例讲解

    源码下载,需要准备两个工程,一个Android工程(AndroidPractice),Android工程中去加载字节码。宁外一个普通的java工程(DexModule),下面看看工程结构。需要注意的是IDynamicLoad这个接口在两个工程中都需要,且包名一致。

    字节码加载工程结构

    字节码加载工程结构

    DexModule工程讲解

    DexModule这个工程中只有两个类,一个是接口IDynamicLoad,这个接口就是文章开始提到的模块升级的关键,只需要提前约定好接口,将接口发布给调用方,具体的实现对调用方完全是透明的,这样就能做到随意的更改接口的实现,宁一个是该接口的实现类DynamicLoad。实现非常简单就是返回一个字符串。

     

    package com.vjson.module;
    
    public interface IDynamicLoad {
        public String dexLoad();
    }
    <span style="font-weight: normal;">package com.vjson.module;
    
    public class DynamicLoad implements IDynamicLoad {
        @Override
        public String dexLoad() {
            return "dexload practice";
        }
    }</span>

    导出jar包

    在导出jar包的时候一定要注意,不要导出IDynamicLoad这个接口文件,因为Android工程中已经有一个接口文件,不然加载字节码的时候会导致字节码冲突。

    导出jar包

    导出jar包

     

    字节码转换

    用前面提到的字节码转换工具,将jar转换为Dalvik VM认识的dex。d2j-jar2dex.sh这个命令的-o参数指定输出文件。

     

    d2j-jar2dex.sh -o module.jar dynamicLoad.jar

     

    然后将生成的module.jar放到手机的sdcard里面。

     

    adb push module.jar /mnt/sdcard/

    加载dex

    加载字节码需要用到DexClassLoader这个类,它负责从jar包中提取(解压缩的一个过程)classes.dex,并且将字节码加载到内存,接下来就通过loadClass方法加载需要的类,看下面的详细代码,注意高亮的行。

     

    package com.vjson.practice;
    
    import java.io.File;
    
    import android.annotation.TargetApi;
    import android.app.Activity;
    import android.os.Build;
    import android.os.Bundle;
    import android.os.Environment;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.Toast;
    
    import com.vjson.dynamicload.R;
    import com.vjson.module.IDynamicLoad;
    
    import dalvik.system.DexClassLoader;
    
    public class MainActivity extends Activity {
        private Button mBtn;
        private IDynamicLoad mDynamicLoad;
    
        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        public static final IDynamicLoad loadByteCode() {
            File jarFile = new File(Environment.getExternalStorageDirectory().toString() + File.separator + "module.jar");
            File optmizedPath = BaseApplication.sInstance.getDir("dex", MODE_PRIVATE);
            DexClassLoader loader = new DexClassLoader(jarFile.getAbsolutePath(),
                    optmizedPath.getAbsolutePath(), null,
                    BaseApplication.sInstance.getClassLoader());
            IDynamicLoad dynaicLoad = null;
    
            try {
                Class<?> clazz = loader.loadClass("com.vjson.module.DynamicLoad");
                dynaicLoad = (IDynamicLoad) clazz.newInstance();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
            return dynaicLoad;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mDynamicLoad = loadByteCode();
            mBtn = (Button) findViewById(R.id.btn);
            mBtn.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    String str = mDynamicLoad.dexLoad();
    
                    Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

     

    注意代码的26行,通过context的getDir(“dex”, MODE_PRIVATE)方法获取应用程序的私有目录,这是由于从Android4.1.2开始由于安全原因,防止代码注入攻击,必须将字节码放到私有目录下面也就是data/data/应用程序包名/。从jar包中提前出来的classes.dex就放在这个目录下面。

    总结

    本文主要介绍了,Android中的字节码加载技术,为接下来的文章Android模块化升级提供一个理论基础,其实最精髓的地方就是定义接口,通过接口调用端和实现端进行通信。在模块化升级中将会讲解jar包的完整性验证和安全性验证。 

  • 相关阅读:
    机器人
    数据库redis
    资料链接汇总
    通用命令【Linux、sql、adb】
    Mysql 计算地址经纬度距离实时位置
    驱散pod
    mysql用户授权
    k8s 命令自动补全
    node_exporter安装
    nginx 跨域配置
  • 原文地址:https://www.cnblogs.com/Niger123/p/4545641.html
Copyright © 2020-2023  润新知