Skip to content

zyj1609wz/Dynamic

Repository files navigation

Dynamic Android 动态加载实践

项目介绍

app 实现动态加载DEx文件

  • DynamicLib 动态加载接口库和接口实现库,并且被打成Dex文件,如:dynamic_dex.jar

  • appdynamic_dex.jar放到app的assets目录下,实现动态加载dex文件。

  • 动态加载普通的类

  • 动态加载Fragment

DynamicActivityHost 实现动态加载APK文件

  • DynamicActivityInterface 动态加载接口库

  • DynamicActivityDemo 动态加载接口实现module, 运行这个module生成DynamicActivityDemo.apk 文件。然后把这个APK文件放到SD卡的根目录.

  • DynamicActivityHost 动态加载手机SD卡根目录的DynamicActivityDemo.apk文件。

Android 动态加载步骤

1、把需要动态加载的类打成Jar包。比如:dynamic.jar

2、把打成的Jar包打成dex包,注意最后打成的dex包也是以.jar结尾的。比如:dynamic.jar -> dynamic_dex.jar

3、把dex包放到可以实现动态加载的工程的assets目录下。

如何把工程打成Jar包

  • 在工程的build.gradle里面添加
task getJar(type: Copy) {
    delete 'build/libs/dynamic.jar'
    from('build/intermediates/bundles/release/')
    into('build/libs/')
    include('classes.jar')
    rename('classes.jar', 'dynamic.jar')
}

//终端输入 gradlew :DynamicLib:getJar 生成jar
getJar.dependsOn(build)
  • 在命令行终端输入 gradlew :DynamicLib:getJar就会看到在modulebuild/libs看到dynamic.jar

如何把Jar包打成dex

  • 找到Android SDK的build-tools\24.0.0 目录,如图所示:

  • dynamic.jar 拷贝到上面的目录,然后打开CMD 输入命令:

dx --dex --output=dynamic_dex.jar dynamic.jar

最后在这个目录生成的dynamic_dex.jar 就是我们需要的dex文件。

Dex包里面有什么?

我们就以dynamic_dex.jar dex包为例,把它解压,可以看到两个文件

动态加载需要注意的事项

1、善于使用接口编程

由于我们最终动态加载的是Dex文件,但是Dex包中的类都不能直接调用。那么怎么调用我们需要的类呢?通常有两种方法,如下

可以看到在例子中,我们用了两个接口Dynamic,FragmentIDynamicImpl实现Dynamic接口;SetFragment实现FragmentI接口。 不过这里需要注意的是:DynamicLib中的接口,需要在app工程里面重新写一份,并且包名要相同,都是com.dyncmic.lib

2、使用Android中的反射初始化控件

操作方法详见:Android 反射-换一种方式编程

具体到本工程的代码如下:

package com.dynamic.lib;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by ${zhaoyanjun} on 2017/3/3.
 */

public class SetFragment extends Fragment implements FragmentI {

    @Override
    public Fragment getFragment() {
        return new SetFragment() ;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //通过反射获取
        int layoutID = getId( getContext() , "layout" , "fragment_main") ;
        View view = inflater.inflate( layoutID , container , false ) ;
        return view ;
    }

    public static int getId(Context context , String className , String name ){
        return context.getResources().getIdentifier( name , className , context.getPackageName() ) ;
    }
}

相关知识详解

  • ClassLoader

java中,有个概念叫做"类加载器"ClassLoader),它的作用就是动态的装载Class文件。标准的java sdk中有一个ClassLoader类,借助这个类可以装载想要的Class文件,每个ClassLoader对象在初始化时必须制定Class文件的路径。

  • DexClassLoader

dex文件是一种经过android打包工具优化后的Class文件,因此加载这样特殊的Class文件就需要特殊的类装载器,所以android中提供了DexClassLoader类。这个类加载器用来从.jar.apk类型的文件内部加载classes.dex文件。通过这种方式可以用来执行非安装的程序代码,作为程序的一部分进行运行。

DexClassLoader 继承关系

DexClassLoader继承BaseDexClassLoader。继承关系如下图:

ClassLoader
   -- BaseDexClassLoader
        -- DexClassLoader 、 PathClassLoader

DexClassLoader 构造函数

DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)

  • 参数1:待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> )

  • 参数2:解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写(安全性考虑),所以只能放在data/data下。

  • 参数3:指向包含本地库(so)的文件夹路径,可以设为null.

  • 参数4:父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()取到。

PathClassLoaderDexClassLoader的区别

  • PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。

  • DexClassLoader可以支持.apk.jar.dex文件,并且会在指定的outpath路径释放出dex文件。

延伸

动态加载本身是插件开发里面的知识,本项目只是动态加载DEX文件。动态加载也是可以加载APK文件的,这里不做说明,感兴趣的自己可以搜索资料学习一下。

更高级的插件开发可以参考已经成熟的插件框架。

参考资料

About

Android 动态加载实践

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages