个人学习总结,菜鸟总结,请大神指教
皮肤自定义更新思路:
1),apk的形式,通过安装子类apk实现皮肤的形式实现换装,
步骤1,是配置 manifest 的文件application 标签添加属性shareUserId="共享的报名"
2,宿主apk 在下载皮肤的地方去下载对应的皮肤apk ,通过一下啊代码获取到皮肤文件对应的上下文。
Context context = createPackageContext("com.zj.skin", Context.CONTEXT_IGNORE_SECURITY);
3,然后通过皮肤apk的上下去获取对应的文件去显示。
//优点是简洁,高效,图片可以同分辨率去获取对应文件夹下的资源文件
// 缺点是,会导致手机安装很多皮肤类apk,并且影响应用的安全性。 破解者可以通过 破解的manifest文件去获取到shareUserId,进而通过反射+Hook技术造成严重损失。
2),通过自定义zip的方式去实现皮肤的替换,可以进行加密等一系列处理避免因为皮肤文件导致的一些麻烦,增加破解者的难度。
步骤1,通过网络请求(可以做token 加密等处理)去下载资源文件,避免被恶意攻击。
2,将下载的包解压到对应的皮肤路径下,然后通过路径去获取皮肤文件。
ZipUtil zipp = new ZipUtil(2049);
System.out.println("begin do zip");
zipp.unZip("/sdcard/skin.zip","/sdcard/Skin_kris");
Toast.makeText(this, "导入成功", Toast.LENGTH_SHORT).show();
Bitmap bitmap= BitmapFactory.decodeFile("/sdcard/tony/skin/skin.png");
BitmapDrawable bd=new BitmapDrawable(bitmap);
btnSet.setBackgroundDrawable(bd);
layout.setBackgroundDrawable(new
BitmapDrawable(BitmapFactory.decodeFile("/sdcard/Skin_kris/skin/bg/bg.png")));
3),热更新技术实现皮肤的替换
原理:Android中有两个类可以做到动态加载:PathClassLoader和DexClassLoader。这两个类都是继承于BaseDexClass
可以常看这个文章:
利用动态加载技术更换皮肤:https://blog.csdn.net/u011068702/article/details/53311437
https://blog.csdn.net/yuanzeyao/article/details/42390431
推荐:
Android插件化开发之DexClassLoader动态加载dex、jar小Demo
http://blog.csdn.net/u011068702/article/details/53263442
Android插件化开发之动态加载基础之ClassLoader工作机制
http://blog.csdn.net/u011068702/article/details/53248960
重点:推荐成熟的框架://换肤功能 compile 'com.zhy:changeskin:4.0.2'
具体集成方式可以查看这篇文章: https://blog.csdn.net/qby_nianjun/article/details/79227099
2, 获取存储到指定路径的apk包信息
/**
* 获取未安装apk的信息
* @param context
* @param apkPath apk文件的path
* @return
*/
private Map<String,String> getUninstallApkInfo(Context context, String apkPath) {
Map hashMap = new HashMap<String,String>();
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
if (null != pkgInfo) {
ApplicationInfo appInfo = pkgInfo.applicationInfo;
String pkgName = appInfo.packageName;//包名
hashMap.put(PKG_NAME, pkgName);
} else {
Log.d(TAG, "program don't get apk package information");
}
return hashMap;
}
3,因为没有安装,所以不能得到context,所以我们需要未安装apk的Resource,我们可以通过反射来获取,
/**
* @param apkPath
* @return 得到对应插件的Resource对象
*/
private Resources getPluginResources(String apkPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
//反射调用方法addAssetPath(String path)
Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class);
//将未安装的Apk文件的添加进AssetManager中,第二个参数是apk的路径
addAssetPath.invoke(assetManager, apkPath);
Resources superRes = this.getResources();
Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(),
superRes.getConfiguration());
return mResources;} catch (Exception e) {
e.printStackTrace();
}
return null;
}
4,核心代码通过DexClassLoader 获取到resId对应的资源文件去设置到对应的位置上面,中间使用了一个反射的方 式去获取到resID.
/**
* 加载apk获得内部资源,并且替换背景
* @param apkDir apk目录
* @param apkName apk名字,带.apk
* @throws Exception
*/
private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception {
//在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建,这个目录主要是最优化目 录,用于缓存dex文件
File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE);
//打印路径 理论上是/data/data/package/app_dex
Log.v(TAG, optimizedDirectoryFile.getPath().toString());
//构建DexClassLoader
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());
//通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id
Class<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE);
//得到名为about_log的这张图片字段,这个图片是为安装apk里面的图片
Field field = clazz.getDeclaredField(IMAGE_ID);
//得到图片id
int resId = field.getInt(R.id.class);
//得到插件apk中的Resource
Resources mResources = getPluginResources(apkPath);
if (mResources != null) {
//通过插件apk中的Resource得到resId对应的资源
Drawable btnDrawable = mResources.getDrawable(resId);
mLayout.setBackgroundDrawable(btnDrawable);
} else {
Log.d(TAG, "mResources is null");
}
}
4),内部定义好的皮肤,通过工具类控制切换,然后广播通知的方式去更新。也可以通过application的方式去注册回调的方 式去通知更新
application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks callback) {
.....
该方法也适用于中英文切换等:
在onActivityCreated 的方法中去
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
LogUtil.d(TAG, "[onActivityCreated] activity:" + activity);
mActivitys.add(activity);
LanguageSettingUtil.changeAppLanguage(activity, LanguageSettingUtil.getLocalLanguage(activity));
}
......
}
缺点:不可由用户增加或修改;apk臃肿
最低的自由度,软件实现相对于后两种最容易。
优点: 适用于简单的皮肤替换,如更换背景色等。