一、换肤种类的功能划分
1) 软件内置多个皮肤,用户不能修改,通过打包时修改Tag来切换展示的布局
2) 在打包时获取不同SVN下不同res文件打包到apk中
3) 软件内置一套默认展示资源文件,后期提供皮肤包,展示时自动加载。
二、皮肤定义
软件皮肤包括图标、字体、布局、交互风格等,换肤就是换掉皮肤包括的部分或所有资源。
三、具体实现
1、实现思路
1)资源共享
资源共享的实现思路是获取其他apk中的资源id,进而通过该id寻找到需要的资源。该资源可以是图片,字符串,drawable一组动态配置等。
获取的这些资源对象必须有Java代码能够实现展示该对象,如setBackground(drawable)、setText(char)、setTextSize(float)等方法,而不可以是setTheme(id),此方法获取返回的id虽然是皮肤包中的资源id但寻找到的资源却是主工程中的内容。
例如所有apk进行反编译后会把所有资源的类型、名称、id存在public.xml中(注:此处类似于R文件),而寻找到相应的名称下的内容。
2)布局共享
利用LayoutInflater.inflate()获取到皮肤apk中的layout并setContentView加载,加载后这个布局将被android系统实例化,而在这个activity中使用activity.findViewById(id)时,其中的id实际是从加载布局中去寻找id组件,所以可以理解为在这个activity下均可以使用利用反射获取到皮肤apk中组件的id直接寻找对象。从而实现动态切换布局的目的。
tabView_local =(TextView) findViewById(SkinUtil.getIdByName("tabView_local",skinContext));
3)数据共享
Android会为每个apk进程分配一个单独的空间(比如只能访问/data/data/自己包名下面的文件),一般情况下apk之间是禁止相互访问数据的。通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中.,可以访问其他APK的数据目录下的数据库和文件.就像访问本程序的数据一样。
apk之间读取数据的条件是:有相同签名并且AndroidManifest.xml中配置android:sharedUserId有相同的属性值,这样两个apk才能互相访问数据。(类似于ContentProvider)方法如下:
应用程序和皮肤程序的AndroidManifest.xml中配置android:sharedUserId="com.shareID"
注:(如果设备内的应用程序和皮肤程序中的UID与当前不符则会在安装时报错INSTALL_FAILED_UID_CHANGED,此时需要卸载设备中的应用重新安装,所以如果要使用数据共享则需从初版就添加此值)
2、实现方法
1)查询本地是否有皮肤包:
此处用的是getPackageManager().getInstalledPackages获取所有已安装app,之后再用正则表达式判断是否是皮肤包
<pre name="code" class="java">/**
* 获取所有已安装的皮肤主题
*
* @return
*/
private ArrayList<PackageInfo> getAllSkin() {
ArrayList<PackageInfo> skinList = new ArrayList<PackageInfo>();
List<PackageInfo> packs = getPackageManager().getInstalledPackages(0);
for (PackageInfo p : packs) {
if (isSkinPackage(p.packageName)) {
skinList.add(p);
}
}
return skinList;
}
/**
* 判断是否是皮肤主题
*
* @param packageName
* @return
*/
private boolean isSkinPackage(String packageName) {
// 自己制作的皮肤主题包名 例如:sunlight.skin0 sunlight.skin1等等
String rex = "com.skin.*";
Pattern pattern = Pattern.compile(rex);
Matcher matcher = pattern.matcher(packageName);
return matcher.find();
}
2)记录需要使用的皮肤包
如果本地APK皮肤包和当前皮肤包中的包名不同则修改本地皮肤包包名,并提示重启app。
当前使用的皮肤包包名存储在SharedPreferencesFactory中
3)获取皮肤包的Context
Context context = createPackageContext(“上一步获取的apk皮肤包包名”,Context.CONTEXT_IGNORE_SECURITY);
获取到皮肤包对应的Context,通过返回的context对象就可以访问到该包中的任何资源。
4)根据Context获取资源
1)使用方法
获取时需用反射的方式获取,此处已在SkinUtil类中封装好实现方法,例如:
public staticView getViewByName(String name, Context context)
public staticView getLayoutByName(String name, Context context)
public staticString getStringByName(String name,Context context)
等,此处只需传入皮肤apk的Context以及待获取的资源名即可。
2)实现方式
1、获取资源ID
skinContext.getResources().getIdentifier(“需要获取的资源文件名称,此处应当在皮肤Apk的R文件存在”, "需要获取文件的父类,例如layout\string\drawable等",“皮肤包名称,此处用skinContext.getPackageName()方法获取”);
2、根据资源ID获取该文件,以下随便写几个例子
<pre name="code" class="java"> /**
* 根据资源获取Context对应的
* @param 资源名称
* @return 资源对应的String
*/
public static String getStringFromStringByName(String name,Context context){
int resourceId = context.getResources().getIdentifier(name, "string", context.getPackageName());
return context.getString(resourceId);
}
/**
* 通过布局名及Context获取布局View对象
* @param name
* @param context
* @return 布局View
*/
public static View getLayoutFormLayoutByName(String name, Context context) {
int resourceId = context.getResources().getIdentifier(name, "layout",context.getPackageName());
Log.e("gzy", "getLayoutByName"+name+":"+Integer.toHexString(resourceId));
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(resourceId, null);
return view;
}
可以实现利用apk的方式进行更换资源文件以及布局