类加载器(DexClassLoader)与插件化(动态加载)

类加载器与插件化解析

2.1 类装载器 DexClassLoader

首先,我们需要了解关于java代码本地import的一些知识:

import中所引用的类有两个特点:

1.必须存在于本地,当程序运行时需要该类时,内部类装载器会自动装载该类,这对程序员来讲是透明的,即程序员感知不到该过程

2.编译时必须在现场,否则编译过程会因为找不到引用文件而不能正常编译。

使用ClassLoader的必要说明,多用于动态加载一些自定义的类。

一般情况下,应用程序不需要创建一个全新的ClassLoader,而是使用当前环境中已经存在的ClassLoader。因为Java的Runtime环境在初始化时,其内部会创建一个ClassLoader对象用于加载Runtime所需的各种Java类。

每个ClassLoader必须有一个父ClassLoader,在装载Class文件时,子ClassLoader会先请求其父ClassLoader加载该Class文件,只有当其父ClassLoader找不到该Class时,子ClassLoader才会继续装载该类,这是一种安全机制。

Android使用的是DexClassLoader。

下面简单介绍下DexClassLoader的使用方法

假设这里有两个APK,第一个叫做Host,第二个叫做Plugin,其中Plugin中定义了一个PluginClass,该类中定义了一个函数,functionl(),代码如下:

public class PluginClass {
        public PluginClass(){
            Log.i( "Plugin" , "PluginClass client initialized" ) ;
        }
        public int functionl( int a, int b ) {
            return a + b;
        }
    }

我们剩下要做的就是在这个Host APK中去掉用我们PluginClass的functionl()方法。

这里用注释解释下代码

public void useDexClassLoader(){
        //确定目标class所在的位置
        Intent intent = new Intent("com.haiii.android.plugin.client",null);
        //通过PackageManager获取信息。
        PackageManager pm = getPackageManager();
        final List<ResolveInfo> plugins = pm.queryIntentActivities(intent,0);
        ResolveInfo info = plugins.get(0);
        ActivityInfo ainfo = info.activityInfo;
        //当存在多个dexPath路径时,需要此分隔符
        String div = System.getProperty("npath.separator");
        //报名
        String packageName = ainfo.packageName;
        //目标全路径
        String dexPath = ainfo.applicationInfo.sourceDir;
        //本地解析输出路径
        String dexOutputDir = getApplicationInfo().dataDir;
        //目标lib(C/C++)文件存放路径
        String libPath = ainfo.applicationInfo.nativeLibraryDir;
        //创建我们自己的ClassLoader
        DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDir,libPath,this.getClass().getClassLoader());
        try {
            //通过反射机制去调用方法
            Class<?> clazz = cl.loadClass(packageName+".PluginClass");
            Object obj = clazz.newInstance();
            Class[] params = new Class[2];
            params[0] = Integer.TYPE;
            params[1] = Integer.TYPE;
            Method action = clazz.getMethod("functionl",params);
            Integer ret = (Integer)action.invoke(obj,12,34);
            Log.i("Host","returnvalueis"+ret);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }catch(Exception e){
            Log.e("Host", "Something wrong has happened!");
        }
    }

上面的注释应该挺明确的,这里主要对DexClassLoader的初始化做下说明:

dexPath,指目标类所在的APK或Jar文件的路径。类装载器将从该路径中寻找指定的目标类,该路径必须是APk或Jar的全路径,比如/data/app/com.haiii.android.plugin.apk。如果要包含多个路径,路径之间必须用特定分隔符进行分割,即上面的System.getProperty(“path.sepator”)。

dexOutputDir,由于dex文件被包含在APk或者Jar中,因此在装载目标类之前需要从APk或jar中解压出dex文件,该参数就是指定解压出的dex文件存放路径。在Android系统中,一个应用程序一般对应一个Linux用户id,应用程序仅对属于自己的数据目录路径有写的权限,因此,该参数可以使用该程序的数据路径。

libPath,指目标类中所使用的C/C++库存放路径。

parent,指该装载器的父装载器,一般为当前执行类的装载器。

通过类装载器装载class之后会返回一个class对象,但这个class对象,我们本地是没有对其定义的,所以我们无法使用它的方法,而且此时也没有生成我们需要的PluginClass对象,只是装载了PluginClass的代码而已。所以这里就需要用到反射去创建对象并调用相应的方法。

2.2 基于类装载器设计的”插件“架构

目前市场上也有不少关于插件化的东西,这里将简单介绍下关于使用类装载器实现插件化的思想与方法。

上面的关于DexClassLoader的介绍,我们发现,整个调用都还算简单,但到了最后使用反射这一块太过于繁琐,既然我们知道我们要调用的类和方法,那么为什么不考虑下,使用接口机制呢??相信看这篇文章的人都有一定的Android基础,那么应该都了解service远程aidl机制里,有一个asInterface的方式,我们可以模仿下。

首先定义一个接口,这个接口仅仅定义函数的输入和输出,不定义具体实现。这个接口要同时参与Host与Plugin两个项目(APK)的编译。

//接口定义如下
public interface Comm{
        public int functionl( int a, int b ) ;
    }

然后修改下PluginClass的代码

public class PluginClass implements Comm{
        public PluginClass(){
            Log.i( "Plugin" , "PluginClass client initialized" ) ;
        }
        public int functionl( int a, int b ) {
            return a + b;
        }
    }

然后在Host中,我们对于Class对象newInstance()之后返回的对象就可以强制转换为Comm接口对象了,修改下代码:

try{
    Class<?> clazz = cl.loadClass(packageName + ".PluginClass");
    Comm comm = (Comm)clazz.newInstance();
    Integer ret = comm.functionl(12,34);
    Log.i("Host","return value is " + ret);
}

这段代码执行结果与上一段代码的结果是相同的。

我们来看下,当前市场中关于插件的描述:

1.一种逻辑概念,不是技术标准;

2.插件不能独立运行,必须运行与一个宿主程序中,由宿主程序去调用插件程序。

3.宿主程序中可以管理不同的插件,包括数量,禁用或使用,主题

设置等。多个插件,应该能做到切换插件。

4.宿主程序需要保证向下兼容,新版本应该能运行老版本的插件。

注意点:

1.接口一般定义在Host中,如本例的Comm.java。

2.Plugin项目中需要使用Comm是,必须通过一个外部的jar包,这个jar包必须是以Library方式添加到Plugin的build

path中,不能以外部jar包的方式添加,因为我们只需要在Host中存在我们定义的接口,而不想在Plugin中也编译进去,如果都编译进去,就会产生包名相同但验证码不同的文件,导致”Class

ref in pre-verified class resolved to unexpected implementation”.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 12:11:32

类加载器(DexClassLoader)与插件化(动态加载)的相关文章

android插件化开发——加载广播

阅读本文前,先阅读前面几篇: http://blog.csdn.net/u013022222/article/details/51171720 引言 在android开发过程中,我们不可避免的会使用广播,比如,侦听开机,侦听短信. 而对于广播,我想很多人都知道他有两种类型,动态广播,通过代码在runtime进行register, 像这样: IntentFilter intentFilter = new IntentFilter("com.chan.plugin.receiver");

Swiper 轮播插件 之 动态加载无法滑动

1.原因:轮播图未完全动态加载完成,即初始化 2.方法一:ajax链式编程 $.ajax({ type: "get", url: serviceURL + "/listBanner" }).done(function(data){ //动态覆盖轮播图父元素中内容 }).done(function(data){ new Swiper('.swiper-container', { autoplay: true, //可选选项,自动滑动 initialSlide :0,

Java重要技术(28)类加载器之类加载器的层次关系和委托加载机制

1.1. 类加载器的层次 类加载器包括三种: Bootstrap ClassLoader:用于加载JRE的lib目录下的jar文件中的class. ExtClassLoader:用于加载JRE的lib/ext目录下的jar文件中的class. AppClassLoader:用于加载classpath下的class. 在加载一个class时,通常应该先委托给parent类加载器来加载,parent类加载器找不到这个类时,才自行加载.实际优先次序从高到低排列是BootStrap ClassLoade

携程Android App插件化和动态加载实践

携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实现细节,回顾携程Android App的架构演化过程,期望我们的经验能帮助到更多的Android工程师. 需求驱动 2014年,随着业务发展需要和携程无线部门的拆分,各业务产品模块归属到各业务BU,原有携程无线App开发团队被分为基础框架.酒店.机票.火车票等多个开发团队,从此携程App的开发和发布

Java-反射之动态加载类

在Java当中,加载类分为动态加载和静态加载,其中,在编译时刻加载类叫做静态加载类,在运行时刻加载类叫做动态加载类. 产生Class对象的方式中,有一个是Class.forName("类的全称"),这个不仅仅表示类的类类型,而且还表示了动态加载类. 1 package com.example.demo; 2 3 public class Demo { 4 public static void main(String[] args) { 5 if (args[0].equals(&quo

Android 插件开发,做成动态加载

为什么需要插件开发: 相信你对Android方法数不能超过65K的限制应该有所耳闻,随着应用程序功能不断的丰富,总有一天你会遇到一个异常: Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536 可能有些同学会说,解决这个问题很简单,我们只需要在Project.proterty中配置一句话就Ok啦, dex.force.jumbo=true 是的,加入了这句话,确实可

eCharts动态加载各省份的数据

假如从数据库读出以下数据,如何将数据展示在地图之上 1.部门的名称数据: List deptname=[联通事业部-上海联通项目组, 联通事业部-河南联通项目组, 联通事业部-贵州联通项目组, 联通事业部-黑龙江联通项目组, 联通事业部-总部项目中心, 联通事业部-河北联通项目组, 联通事业部-北京联通项目组, 联通事业部-西藏联通项目组, 联通事业部-湖北联通项目组, 联通事业部-江苏联通项目组, 联通事业部-宝马业务拓展部, 联通事业部-浙江联通项目组, 联通事业部-重庆联通项目组, 联通事

Android插件化开发之DexClassLoader动态加载dex、jar小Demo

一.温故动态加载ClassLoader机制 如果对Android的ClassLoader加载机制不熟悉,猛戳Android插件化开发动态加载基础之ClassLoader工作机制 http://blog.csdn.net/u011068702/article/details/53248960 二.介绍 我们知道在Android中可以跟java一样实现动态加载jar,但是Android使用德海Dalvik VM,不能直接加载java打包jar的byte code,需要通过dx工具来优化Dalvik

Android插件化(二):使用DexClassLoader动态加载assets中的apk

Android插件化(二):使用DexClassLoader动态加载assets中的apk 简介 上一篇博客讲到,我们可以使用MultiDex.java加载离线的apk文件.需要注意的是,apk中的类是加载到当前的PathClassLoader当中的,如果apk文件过多,可能会出现ANR的情况.那么,我们能不能使用DexClassLoader加载apk呢?当然是可以的!首先看一下Doc文档. A class loader that loads classes from .jar and .apk