Android 如何在一个app程序中动态加载另一个app程序中的类
本人小白,初次写博客,欢迎大家指点 ~~
问题描述:
假设在Android手机上有两个android应用,一个名为Host,另一个名为Client,现在有这样一个需求:在Client里面有一个类ClientPrint,需要在Host中调用这个ClientPrint类,并执行该类的内部方法,应该如何实现。
ClientPrint的具体实现如下:
public class ClientPrint {
public void print() {
Log.e("ClientPrint","hello world");
}
}
相关的知识点:
1、利用Java的反射机制,动态加载ClientPrint
Dalvik虚拟机中有这样的一个类DexClassLoader,类似于JVM中的ClassLoader的作用,它的构造方法如下:
/ *
* dexPath *.dex文件的路径
* dexOutputPath 解压出来的.dex文件存放的目录
* libPath C语言链接库的路径
* classLoader 类加载器
*/
public DexClassLoader(String dexPath,String dexOutputPath,String libPath,ClassLoader classLoader);
在此对上面的参数做一下说明:
我们知道,当一个程序被安装到手机上时,相应的.apk文件会被复制到手机分配的硬盘空间上,每当程序运行时,相应的 classes.dex文件和资源文件都会从这个apk中解压出来(apk文件其实也是一个zip包),然后被程序调用。
那么这些将要被解压的文件一般存放在手机哪个地方呢?
一般classes.dex文件存放在: /data/app/应用程序包名-1.apk
而被Java本地调用的C语言动态链接库则存放在:/data/app-lib/应用程序包名-1
所以上述参数的意义是这样的:dexPath代表将要被加载的类的路径,libPath代表程序需要调用C语言链接库的路径,dexOutputPath指明了解压出来的classes.dex文件存放的路径,classLoader就不做过多解释了,就是Java反射机制需要的类加载器。
现在万事俱备,可以着手加载ClientPrint类了,代码如下:
DexClassLoader dcl = new DexClassLoader(dexPath,dexOutputPath,libPath,this.getClass().getClassLoader());
try {
//参数 packageName 是ClientPrint的包路径
Class<?> clazz = dcl.loadClass(packageName + ".ClientPrint");
Object obj = clazz.newInstance();
Method method = clazz.getMethod("print",null);
//成功调用方法
method.invoke(obj,null);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
总结一下:从dexPath和libPath中分别解压出classes.dex和C语言动态链接库文件,并将解压结果存放到dexOutputPath,利用Java反射机制从中动态加载这些类。
2、相关路径的获取
为了得到目标程序的相关信息。我们可以在目标程序中新建一个空的Activity,并在清单文件中配置action节点属性,这样可以方便我们用Intent来获取Client程序的相关信息。
代码如下:
package com.example.client;
import android.app.Activity;
import android.os.Bundle;
public class EmptyActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
AndroidManifest.xml配置
<activity
android:name=".EmptyActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.example.client.emptyactivity"/>
</intent-filter>
</activity>
这样在Host程序中就可以通过Intent来获取Client的信息
Intent it = new Intent("com.example.client.emptyactivity",null);
PackageManager pm = getPackageManager();
List<ResolveInfo> queryIntentActivities = pm.queryIntentActivities(it,0);
ResolveInfo rinfo = queryIntentActivities.get(0);
ActivityInfo ainfo = rinfo.activityInfo;
//注意,在此处获取路径
String dexPath = ainfo.applicationInfo.sourceDir;
String dexOutputPath = getApplicationInfo().dataDir;
String libPath = ainfo.applicationInfo.nativeLibraryDir;
全部源码
首先是Client部分
EmptyActivity.java
package com.example.client;
import android.app.Activity;
import android.os.Bundle;
public class EmptyActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
AndroidManifest.xml节点属性配置
<activity
android:name=".EmptyActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.example.client.emptyactivity"/>
</intent-filter>
</activity>
ClientPrint类
package com.example.client;
import android.util.Log;
public class ClientPrint{
@Override
public void print() {
// TODO Auto-generated method stub
Log.e("ClientPrint","hello world");
}
}
Host部分
package com.example.host;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import dalvik.system.DexClassLoader;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@SuppressLint("NewApi") public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/*
* 当屏幕上的Button被点击后,会触发这个方法
*/
public void press(View view) {
Intent it = new Intent("com.example.client.emptyactivity",null);
PackageManager pm = getPackageManager();
List<ResolveInfo> queryIntentActivities = pm.queryIntentActivities(it,0);
ResolveInfo rinfo = queryIntentActivities.get(0);
ActivityInfo ainfo = rinfo.activityInfo;
String packageName = ainfo.packageName;
String dexPath = ainfo.applicationInfo.sourceDir;
String dexOutputPath = getApplicationInfo().dataDir;
String libPath = ainfo.applicationInfo.nativeLibraryDir;
DexClassLoader dcl = new DexClassLoader(dexPath,dexOutputPath,libPath,this.getClass().getClassLoader());
try {
Class<?> clazz = dcl.loadClass(packageName + ".ClientPrint");
Obj obj = clazz.newInstance();
Method method = clazz.getMethod("print",null);
method.invoke(obj,null);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
同时安装Host.apk和Client.apk,然后运行Host,会发现控制台上输出”Hello world”.
后记
可以在Host和Client同时定义一个接口,让ClientPrint继承它,这样在Host之中调用会方便些。在具体实现时,可以在Host中定义接口,然后再Client中导入这个接口的jar包。但是一定要注意,要以Library形式导入这个jar包,否则会因为Host和Client程序中有同名的接口而冲突。