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

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

在正式开篇之前,可能很多人在网上查找,主Host APKplugin之间还需要设置相同的sharedUserId,但是我下面没有做这个要求,因为设置了sharedUserId即代表主Host APKplugin在同一个进程,这样可以辨识对方”,主要是方便主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);

可以查一下getIdentifierAPI使用,这个方法将第二个参数改为”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 即插件apkR类中drawable资源,这里drawable相当于R的类中类,对于类种类的访问,反射通过用”$”符号将其连接.这里可以一次类推,如果是获取String资源,那么com.oneplus.oneplusplugin.R$drawable改为com.oneplus.oneplusplugin.R$String,其他类型同理,当然在后面调用时是字符串不是图片了.


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

 

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



整个工程源码在后续会通过github提供








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值