您的位置:首页 > 移动开发 > Android开发

Android中的类加载器

2016-05-22 21:14 597 查看

概述

在讲解类加载器之前,我们先看一张从Android项目打包成apk的一个过程



分类

Android 中有三个 ClassLoader, 分别为URLClassLoader、PathClassLoader、DexClassLoader。其中

URLClassLoader 只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。

它只能加载已经安装的apk。因为 PathClassLoader 只会去读取 /data/dalvik-cache 目录下的 dex 文件。例如我们安装一个包名为com.hujiang.xxx的 apk,那么当 apk 安装过程中,就会在/data/dalvik-cache目录下生产一个名为data@app@com.hujiang.xxx-1.apk@classes.dex的 ODEX 文件。在使用 PathClassLoader 加载 apk 时,它就会去这个文件夹中找相应的 ODEX 文件,如果 apk 没有安装,自然会报ClassNotFoundException

DexClassLoader 是最理想的加载器。它的构造函数包含四个参数,分别为:

1.dexPath,指目标类所在的APK或jar文件的路径.类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)获得.

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

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

4.classload,是指该装载器的父装载器,一般为当前执行类的装载器

为了便于大家理解,我截了一张图,展示了dexPath和libPath在Android中的路径



补充知识

由于Java的Runtime环境在初始化时内部会创建一个ClassLoader对象用于加载Runtime所需的各种Java类,所以一般不需要创建一个新的ClassLoader对象,而是使用当前环境已经存在的ClassLoader对象。

每个ClassLoader必须有一个父ClassLoader,在装载class文件时,子ClassLoader会先请求其父ClassLoader加载class文件,只有当父ClassLoader找不到class文件时,子ClassLoader才会继续装载该类

案例

下面我们通过一个例子来说明一下DexClassLoader的用法

假设有两个APK,一个叫app,一个叫Plugin,其中Plugin定义了一个PlugClass,该类中定义了一个函数function(),然后在app中我们去动态加载Plugin,调用它的function函数



public class PluginClass {
private static final String TAG = "PluginClass";
public PluginClass(){
Log.d(TAG, "PluginClass: initialized");
}
public int function(int a,int b){
return a+b;
}
}


同时我们给Plugin中的MainActivity定义一个Action

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="com.intent.action.classloader"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>


然后我们编写app中的MainActivity如下

public class MainActivity extends AppCompatActivity {

private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textview);
testDexClassLoader();
}
public void testDexClassLoader(){
//定义intent,指定的action要与plugin中的一致
Intent intent = new Intent("com.intent.action.classloader");
PackageManager packageManager = getPackageManager();
List<ResolveInfo> infos = packageManager.queryIntentActivities(intent, 0);
ResolveInfo info = infos.get(0);
ActivityInfo activityInfo = info.activityInfo;

String packageName = activityInfo.packageName;
Log.d(TAG, "testDexClassLoader: "+packageName);//com.example.plugin
String sourceDir = activityInfo.applicationInfo.sourceDir;
Log.d(TAG, "testDexClassLoader: "+sourceDir);// /data/app/com.example.plugin-1.apk
String dataDir = getApplicationInfo().dataDir;
Log.d(TAG, "testDexClassLoader: "+dataDir);// /data/data/com.example.host
String nativeLibraryDir = activityInfo.applicationInfo.nativeLibraryDir;
Log.d(TAG, "testDexClassLoader: "+nativeLibraryDir);// /data/app-lib/com.example.plugin-1

DexClassLoader classLoader = new DexClassLoader(sourceDir,dataDir,nativeLibraryDir,getClass().getClassLoader());
try {
Class<?> aClass = classLoader.loadClass(packageName + ".PluginClass");
Object object = aClass.newInstance();
Method method = aClass.getMethod("function", new Class[]{Integer.TYPE, Integer.TYPE});
Integer result = (Integer) method.invoke(object, 3, 4);
mTextView.setText(result+"");
} catch (Exception e) {
e.printStackTrace();
}
}


我们看看运行的结果



接下来我们再看看PathClassLoader的用法

同样,我们在plugin中定义一个Animal类

public class Animal {

public String sayHello(){
return "Hello";
}
}


然后我们在app中编写如下代码

public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textview);
testPathClassLoader();
}
public void testPathClassLoader(){
String packageName = "com.example.plugin";
String classPath = "com.example.plugin.Animal";
try {
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
String sourceDir = applicationInfo.sourceDir;
Log.d(TAG, "testPathClassLoader: "+sourceDir);// /data/app/com.example.plugin-2.apk
PathClassLoader pathClassLoader = new PathClassLoader(sourceDir,ClassLoader.getSystemClassLoader());
Class<?> aClass = Class.forName(classPath, true, pathClassLoader);
Object object = aClass.newInstance();
Method method = aClass.getMethod("sayHello", null);
String result= (String) method.invoke(object, null);
mTextView.setText(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}




总结

学习类加载器有什么用?

对Android工程的编译和打包进行自动化,比如建立每日构建系统,自动生成发布文件等

通过下载插件加载的方式实现功能扩展

有些功能性模块,或是第三方开发,或是分阶段开发的,为了方便程序的扩展,我们同样考虑将功能代码封装成插件的形式
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: