Android动态加载Jar小记

前言

虽然之前公司的项目有用到动态加载技术,但是并没有太在意,今天突然看到别人的app中有用到动态加载,打算了解一下。

概述

Android使用Dalvik虚拟机加载可执行程序,所以不能直接加载基于class的jar,而是需要将class转化为dex字节码,从而执行代码。优化后的字节码文件可以存在一个.jar中,只要其内部存放的是.dex即可使用。

实验步骤

Android Studio为IDE

1.创建动态加载模块
1.1 在工程中新建需要实现动态加载的模块,并添加测试类,如下图
这里写图片描述
其中base-lib表示动态加载模块,DynamicTest表示要动态加载的类,IDynamic类用于DynamicTest的父类接口。
1.1.1.创建IDynamic接口

package com.lgf.plugin;

/**
 * 打包时不能打包此类
 */

public interface IDynamic {
    String show();
}

1.1.2 创建一个DynamicTest 类实现IDynamic 接口

package com.lgf.base;

import com.lgf.plugin.IDynamic;

public class DynamicTest implements IDynamic {
    @Override
    public String show() {
        return "DynamicTest";
    }
}

1.2 打包jar
在该模块的build.gradle中添加打包的task

apply plugin: 'com.android.library'

android {
   ...省略一百字
}

dependencies {
     ...省略一百字
}

task buildJar(dependsOn: ['assembleRelease'], type: Jar) {
    archiveName = "origin.jar" // 打包普通jar的名字
    from('build/intermediates/classes/release/com/lgf/base/')
    into('com/lgf/base/')
    exclude 'com/lgf/plugin/IDynamic.class' // 去除IDynamic类
}

// 执行此方法生成dex的jar
task exportPluginJar(dependsOn: ['buildJar'], type: Exec) {
    def outJar = buildJar.archivePath.getAbsolutePath()
    commandLine "dx.bat", "--dex", "--output=\"build/libs/plugin.jar\"", "${outJar}"
}

1.3 命令行窗口执行如下命令,即可在模块的build/libs目录下生成plugin.jar

gradlew exportPluginJar

注意:需要将dx添加到系统环境变量中(要将普通的jar转换为包含dex的jar,可使用Android SDK自带的dx工具,可将dx加入到系统的环境变量中,方便使用(注意大部分SDK的dx文件是在platform-tools文件夹下,也有一些是在build-tools文件夹下)。

1.4 如果不用1.2和1.3步骤,也可手动打jar:到要转换的jar(这里假设为origin.jar)目录,执行

dx --dex --output=plugin.jar origin.jar

会发现在该目录下生成了一个plugin.jar文件,这个文件就是android工程可以加载的文件。
1.5.将生成的jar放入手机SD卡
进入该模块的build/libs目录,执行如下命令

adb push plugin.jar  /sdcard

2.主模块使用动态加载
2.1 复制动态加载模块中IDynamic类到主模块,包名也要设置成一样的,如下图
这里写图片描述
2.2 实现动态加载

package com.lgf.dynamicload.demo;

import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

import com.lgf.plugin.IDynamic;

import java.io.File;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // layout相应的View中使用onClick属性
    public void showMessage(View view) {
        // 此路径为插件存放路径
        File dexPathFile = new File(Environment.getExternalStorageDirectory() + File.separator + "plugin.jar");
        String dexPath = dexPathFile.getAbsolutePath();
        String dexDecompressPath = getDir("dex", MODE_PRIVATE).getAbsolutePath(); // dex解压后的路径
//        String dexDecompressPath = Environment.getExternalStorageDirectory().getAbsolutePath(); // 不能放在SD卡下,否则会报错
        /**
         * DexClassLoader参数说明
         * 参数1 dexPath:待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> )
         * 参数2 optimizedDirectory:解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写(安全性考虑),所以只能放在data/data下。本文getDir("dex", MODE_PRIVATE)会在/data/data/**package/下创建一个名叫”app_dex1“的文件夹,其内存放的文件是自动生成output.dex;如果不满足条件,Android会报错误
         * 参数3 libraryPath:指向包含本地库(so)的文件夹路径,可以设为null
         * 参数4 parent:父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()获取到。
         */
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath, dexDecompressPath, null, getClassLoader());
        Class libClazz = null;
        try {
            libClazz = dexClassLoader.loadClass("com.lgf.base.DynamicTest");
            IDynamic lib = (IDynamic) libClazz.newInstance();
            Toast.makeText(this, lib.show(), Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.3 添加权限
由于要读取sd卡的文件,因此需要添加读取权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Demo

参考

android动态加载
Android动态加载dex技术初探

猜你喜欢

转载自blog.csdn.net/fengyulinde/article/details/79623743