Android应用有时候会涉及到皮肤的更换问题,在这里,我用一种引用其它已安装或未安装apk文件的资源来说明。
其核心思想就是利用反射来获取。
a、引用其它未安装apk文件的资源来说明
1、首先创建一个application(StyleClient),将其打包称APK并拷贝到/data/目录下
2、在StyleClient里的Drawable文件夹下随便拷贝进去一张图片,OK。
3、再创建一个application(StyleHost),编写代码,下面附上。
b、引用其它已安装apk文件的资源来说明
基本步骤同上,只是,要把StyleClient安装一下,不用拷贝。
核心代码:
package org.zqy.stylehost;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RelativeLayout;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RelativeLayout tre = (RelativeLayout) findViewById(R.id.root);
Button btn_1 = (Button) findViewById(R.id.btn_1);//获取未安装的图片
Button btn_2 = (Button) findViewById(R.id.btn_2);//获取已安装的图片
btn_2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
tre.setBackground(getTestBContext().getResources()
.getDrawable(
getId(getTestBContext().getResources(),
"drawable", "xiangbi")));
} catch (NotFoundException e) {
e.printStackTrace();
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
});
btn_1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 要操作的apk的位置
String dexPath = "/data/StyleClient.apk";
// 把apk解压到data\data\当前应用程序
String dexOuputPath = getApplicationInfo().dataDir;
// C/C++类库位置
String libPath = null;
DexClassLoader localDexClassLoader = new DexClassLoader(
dexPath, dexOuputPath, libPath, this.getClass()
.getClassLoader());
try {
// 反射drawable内部类
Class<?> localClass1 = localDexClassLoader
.loadClass("org.zqy.styleclient.R$drawable");
// 反射内部类的属性
Field[] fields = localClass1.getDeclaredFields();
for (Field f : fields) {
// 将其属性设置为可读
f.setAccessible(true);
System.out.println(f.getName());
if (f.getName().equals("shui")) {
int id = f.getInt(new R.id());
// 设置背景
tre.setBackground(getPackageResource().getDrawable(
id));
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
// 构建apk里的资源
private Resources getPackageResource() {
try {
// 反射出资源管理器
Class<?> class_AssetManager = Class
.forName("android.content.res.AssetManager");
// 创建类的实例
Object assetMag = class_AssetManager.newInstance();
// 声明方法,因为addAssetPath是被隐藏的,所以只能通过反射调用
Method method_addAssetPath = class_AssetManager.getDeclaredMethod(
"addAssetPath", String.class);
// 执行方法
method_addAssetPath.invoke(assetMag, "/data/StyleClient.apk");
// 为下一行传递参数用的
Resources res = getResources();
// 确定用哪个构造函数
Constructor<?> constructor_Resources = Resources.class
.getConstructor(class_AssetManager, res.getDisplayMetrics()
.getClass(), res.getConfiguration().getClass());
// 执行构造函数
res = (Resources) constructor_Resources.newInstance(assetMag,
res.getDisplayMetrics(), res.getConfiguration());
// 返回/data/StyleCilent.apk的resource实例
return res;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取TestB的Context
*
* @return
* @throws NameNotFoundException
*/
private Context getTestBContext() throws NameNotFoundException {
return createPackageContext("org.zqy.styleclient",
Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
}
// 获取已安装APK资源的id
private int getId(Resources testb, String resType, String resName) {
return testb.getIdentifier(resName, resType, "org.zqy.styleclient");
}
}
代码地址: