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

Android 通过反射综合应用-获取插件Plugin资源

2015-12-08 20:27 507 查看
通过前面的基础内容,做一个Android 资源更新的插件应该没有问题,读者只要将插件的apk当做资源包就可以了,需要更新的资源全部打包到插件包中.

在正式开篇之前,可能很多人在网上查找,主Host APK和plugin之间还需要设置相同的sharedUserId,但是我下面没有做这个要求,因为设置了sharedUserId即代表主Host
APK和plugin在同一个进程,这样可以”辨识对方”,主要是方便主Host更加准确的查找到自己的plugin,其实个人认为可以有必要设,但是单纯从技术角度,这个不会有影响,因为在处理时,无论什么apk(或者dex等)是相同的.

另外,很多网友看到类似的文章,很多博客把下面可以获取资源就认为可以更换APK的皮肤外观了,但是我觉得还差的远,不过下面的确提供了入门的思路.

 

下面我们通过具体的实例看看如何实现.

<1>: 新建一个工程,工程树如下:

在工程中写一个接口类.

<2> : 再新建插件工程,工程树如下:

<3> : 上面host工程和插件工程的接口是完全一样的.具体代码如下:

/**
*
*/
package com.oneplus.plugin.interfaces;

import android.content.Context;
import android.graphics.drawable.Drawable;

/**
* @author zhibao.liu
* @date 2015-11-20
* @company : oneplus.Inc
*/
public interface PluginInterface {

void ConnectToPlugin();

}

现在里面增加一个测试方法!

 

<4> : 在插件工程中添加实现上面接口的类PluginInterfaceImpl.java,代码如下:

/**
*
*/
package com.oneplus.oneplusplugin;

import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.Log;

import com.oneplus.plugin.interfaces.PluginInterface;

/**
* @author zhibao.liu
* @date 2015-11-20
* @company : oneplus.Inc
*/
public class PluginInterfaceImpl implements PluginInterface {

private final static String TAG="oneplus";
@Override
public void ConnectToPlugin() {
// TODO Auto-generated method stub

Log.i(TAG,"PluginInterfaceImpl from plugin project !");

}

}

<5> : Host工程中添加布局如下oneplus_host.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".OneplusHostActivity" >

<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/oneplus_connect"
android:text="@string/oneplus_checkplugin"/>

</RelativeLayout>

同时新增加一个oneplus_string.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="oneplus_checkplugin">check plugin</string>
</resources>

主工程类OneplusHostActivity.java添加如下:

private void OneplusLoaderPlugin(String intentname,String packagename,Context context){

Intent intent = new Intent(intentname, null);
// package manager
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
// activity information
ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
// jar in apk direction
String apkPath = actInfo.applicationInfo.sourceDir;
// native code direction
String libPath = actInfo.applicationInfo.nativeLibraryDir;
PathClassLoader pcl = new PathClassLoader(apkPath, libPath,
this.getClassLoader());

try {
Class clazz=pcl.loadClass(packagename);
try {
Object obj=clazz.newInstance();

try {
Method method=clazz.getMethod("ConnectToPlugin", new Class[]{});
method.setAccessible(true);

try {
method.invoke(obj, new Object[]{});
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

} catch (NoSuchMethodException 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 (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

其中PathClassLoader类在第一节就介绍了.后面Class clazz=pcl.loadClass(packagename);反射插件包的类,然后调用反射类中的方法.在主工程增加了一个按钮,添加按钮事件如下:

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
int resid=v.getId();

switch(resid){
case R.id.oneplus_connect:
OneplusLoaderPlugin(ONEPLUS_PLUGIN_ACTION,ONEPLUS_PLUGIN_PACKAGE_NAME,OneplusHostActivity.this);
break;
default:
break;
}

}

上面两个常量:

private final static String ONEPLUS_PLUGIN_ACTION="oneplus.action.plugin";
private final static String ONEPLUS_PLUGIN_PACKAGE_NAME="com.oneplus.oneplusplugin.PluginInterfaceImpl";

主Host完成以后,还需要对插件的manifest文件进行配置:

 

删除application下面的:
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"

以及activity标签下的:

<category android:name="android.intent.category.LAUNCHER" />

因为插件不需要运行和显示在桌面上!!!

 

 经过上面的整顿,首先先讲plugin的apk安装到手机里面,然后将主工程Host运行,运行结果如下:

看到上面的结果,表明Host和Plugin可以”通信”了,下面看看plugin如何传递资源信息,首先介绍第一种:从插件包中获取一张图片, screen_show_1.png放到plugin工程资源drawable文件夹下.下面是随便截了一张图片

<1> : 在接口类中继续声明一个方法:

Drawable getImageResource(Context context,String name, String packageName);

<2> : 插件工程实现上面接口类如下:

@Override
public Drawable getImageResource(Context context, String name, String packageName) {
// TODO Auto-generated method stub

if(context==null){
return null;
}

PackageManager mPm=context.getPackageManager();
try {

Resources res=mPm.getResourcesForApplication(packageName);
int resid=res.getIdentifier(name, "drawable", packageName);

return res.getDrawable(resid);

} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

<3> : 接着在主Host工程中添加下面的方法来获取图片资源:

private void OneplusLoaderDrawablePlugin(String intentname,String packagename,Context context){

Intent intent = new Intent(intentname, null);
// package manager
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
// activity information
ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
// jar in apk direction
String apkPath = actInfo.applicationInfo.sourceDir;
// native code direction
String libPath = actInfo.applicationInfo.nativeLibraryDir;
PathClassLoader pcl = new PathClassLoader(apkPath, libPath,
this.getClassLoader());

try {
Class clazz=pcl.loadClass(packagename);

try {

Object obj=clazz.newInstance();
PluginInterface plugin=(PluginInterface) clazz.newInstance();

Drawable draw=plugin.getImageResource(OneplusHostActivity.this, "screen_show_1", actInfo.packageName);
if(OneplusImage!=null){
OneplusImage.setImageDrawable(draw);
}

} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

程序解释:

int resid=res.getIdentifier(name,
"drawable", packageName);
可以查一下getIdentifier的API使用,这个方法将第二个参数改为”value”就可以获取string,color等值类型的资源,如果改为”layout”,当然就可以获取布局资源了,也可以获取raw目录下的资源.

运行结果:

上面有一个问题,如果我们的插件根本没有安装,而仅仅放在移动某个目录下,那么需要操作才能够获取呢?下面讲一个更加通用的方法,步骤如下:

<1> : 在主Host工程在增加一个按钮UI,并且听见点击事件.

并且增加一个恒量:

private final static String ONEPLUS_PLUGIN_RESOURCE="com.oneplus.oneplusplugin.R$drawable";

另外将OneplusAndroidPlugin.apk push到手机里面:

因为要访问sdcard路径,所以主Host配置文件中需要加权限:

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

<2> : 关键程序如下 :

private void OneplusLoaderDrawableFlexPlugin(String resname,String packagename){

String dexPath = Environment.getExternalStorageDirectory().toString()
+ File.separator + "OneplusAndroidPlugin.apk";

File file=new File(dexPath);

if(!file.exists()){
return ;
}

final File optimizedDexOutputPath = getDir("outdex", 0);

DexClassLoader cl = new DexClassLoader(dexPath, optimizedDexOutputPath.getAbsolutePath(), null,
getClassLoader());

try {
Class clazz=cl.loadClass(packagename);

try {
Object obj=clazz.newInstance();

try {

Field field=clazz.getDeclaredField(resname);

Object ret=field.getInt(obj);

//following put plugin apk to resource path so that others can find it
AssetManager aMgr=AssetManager.class.newInstance();

try {

Method method=aMgr.getClass().getDeclaredMethod("addAssetPath", new Class[]{String.class});

try {
method.invoke(aMgr, new Object[]{dexPath});

Resources res=this.getResources();
Resources resouces=new Resources(aMgr,res.getDisplayMetrics(),res.getConfiguration());

int resid=Integer.parseInt(ret.toString());

if(OneplusImage!=null){
OneplusImage.setImageDrawable(resouces.getDrawable(resid));
}

} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

} catch (NoSuchFieldException 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 (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

程序解释:

<1>:
AssetManager aMgr=AssetManager.class.newInstance();

try {

Method method=aMgr.getClass().getDeclaredMethod("addAssetPath", new Class[]{String.class});

try {
method.invoke(aMgr, new Object[]{dexPath});

… …

由于插件apk只是push到移动设备任意目录下,那么首先工作是将其增加到系统资源路径下,这样可以让其被其他APP调用,因为AssetManager里面的addAssetPath方法不是对普通应用开发者公开的,所以通过反射将其调用,利用这个方法将资源包置于系统资源路径下.

<2>: 
Resources res=this.getResources();
Resources resouces=new Resources(aMgr,res.getDisplayMetrics(),res.getConfiguration());

获取系统资源Resources对象.

<3>: 
Field field=clazz.getDeclaredField(resname);

Object ret=field.getInt(obj);

这一段反射com.oneplus.oneplusplugin.R$drawable 即插件apk中R类中drawable资源,这里drawable相当于R的类中类,对于类种类的访问,反射通过用”$”符号将其连接.这里可以一次类推,如果是获取String资源,那么com.oneplus.oneplusplugin.R$drawable改为com.oneplus.oneplusplugin.R$String,其他类型同理,当然在后面调用时是字符串不是图片了.

我在代码中也会把获取String和其他资源的方式尽量添加完全,但是根据上面的,个人觉得只要学会举一反三,其他资源也不是难题.

 

为了以防万一,我在这里就不列出String,Color等信息获取,但是在我的测试demo中,我还是给出了如何获取等操作:

整个工程源码在后续会通过github提供
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: