皮肤自定义更新思路

个人学习总结,菜鸟总结,请大神指教

皮肤自定义更新思路:
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


步骤1, 先制作自己的皮肤apk
       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臃肿


                 最低的自由度,软件实现相对于后两种最容易。
 
优点: 适用于简单的皮肤替换,如更换背景色等。


猜你喜欢

转载自blog.csdn.net/u013279840/article/details/80022353