Android应用换肤总结
换肤,我们都很熟悉,像XP的主题,塞班的主题。看过国外的一些技术博客,就会发现国内和国外对软件的,或者说移动开发的软件的需求的不同。国外用户注重社交、邮件等功能,国内用户则重视音乐、小说、皮肤等功能,国内有很多的软件都支持换肤,像腾讯微博、墨迹天气等。
Android系统通过XML 形式描述各种资源,包括布局、字符串、样式、交互风格等,通常存放在res目录中,使应用逻辑和界面设计分离开来,让开发者可以专注于代码逻辑。
换肤就是换掉皮肤包括的部分或所有资源。皮肤一般含有多个文件,例如图片、配置等文件,分散的文件不利于传输和使用,最好打包。打包的格式一般选择zip格式。这里分两种情况,一种是apk,例如腾讯微博,它的皮肤格式是一个apk;另一种是自定义扩展名,例如小米主题的扩展名为mtz,墨迹天气皮肤扩展名是mja,搜狗输入法皮肤扩展名为ssf,本质都是zip格式。
本节课程就来讲解Android应用程序如何实现换肤功能。
软件换肤从功能上可以划分三种:
1) 软件内置多个皮肤,不可由用户增加或修改;
最低的自由度,软件实现相对于后两种最容易。
2) 官方提供皮肤供下载,用户可以使用下载的皮肤;
用户可以下载自己喜欢的皮肤,然后安装使用,如腾讯微博中就提供了四种皮肤,用户可以下载使用。
3) 官方提供皮肤制作工具或方法,用户可自制皮肤。
这种方式使用户有参与感,自由度较高。用户可根据自己的喜好定制软件的皮肤。
软件换肤从实现上可以划分三种:
1.一个应用中放置几个固定的皮肤,皮肤之间的控制由程序来控制实现。
优点:实现简单。
缺点:拥有多套皮肤(图片,布局文件定义),动态的修改布局。这样会让工程比较大,不仅不够灵活,而且生成的安装包太大,要增加新的皮肤需更新应用。
实现技术:这个很简单,可以通过sharereference统一控制皮肤的前后缀或者路径来实现一个通用的换肤目的。
2. zip压缩包式皮肤。应用可设置一个默认路径。如果用户选择某个皮肤,则解压该皮肤.zip到这个文件夹中。
优点:无需安装,即使卸载掉皮肤应用,该皮肤仍然可用,不会影响正常使用。
缺点:可能会因为用户的误操作,使皮肤文件损坏,资源访问的效率低。
实现技术: 该技术重点在于如何去读取zip文件中的资源以及皮肤文件存放策略。
方案:如果软件每次启动都去读取SD卡上的皮肤文件,速度会比较慢。较好的做法是提供一个皮肤设置的界面,用户选择了哪一个皮肤,就把那个皮肤文件解压缩到”/data/data/[package name]/skin”路径下,这样不需要跨存储器读取,速度较快,而且不需要每次都去zip压缩包中读取,不依赖SD卡中的文件,即使皮肤压缩包文件被删除了也没有关系。
实现方法:
1. 在软件的帮助或者官网的帮助中提示用户将皮肤文件拷贝到SD卡指定路径下
2. 在软件中提供皮肤设置界面。可以在菜单或者在设置中。可参考墨迹天气(zip格式)等支持换肤的软件。
3. 加载指定路径下的皮肤文件,读取其中的缩略图,在皮肤设置界面中显示,将用户选中的皮肤文件解压缩到”/data/data/[package name]/skin”路径下。
4. 软件中优先读取”/data/data/[package name]/skin/”路径下的资源。如果没有则使用apk中的资源.
实现实例:墨迹天气下载的皮肤就是一个mja格式的压缩包,在应用的时候把皮肤资源释放到墨迹天气应用的目录下,更换皮肤时新的皮肤资源会替换掉老的皮肤资源每次加载的时候就是从手机硬盘上读取图片,这些图片资源的命名和程序中的资源的命名保持一致,一旦找不到这些资源,可以选择到系统默认中查找。(注:墨迹天气应用在设置中可以让用户自己控制插件皮肤是否装入内存)
下面是一个简单的例子:
下载zip资源包(略)----->解压zip到指定路径,应用程序加载时加载该路径下的资源。
private String basePathString = "/data/data/com.wts.skin/Skin_kris";
private String skinBasePath = basePathString + "/skin/";
//把zip格式的资源包解压到指定路径下:
ZipUtil zipp = new ZipUtil(2049);
zipp.unZip("/sdcard/skin.zip",basePathString);
//为button和背景设置资源
Bitmap bmp =BitmapFactory.decodeFile(skinBasePath+"test.png");
BitmapDrawable bd=new BitmapDrawable(bmp);
btnSet.setBackgroundDrawable(bd);
layout.setBackgroundDrawable(new BitmapDrawable(
BitmapFactory.decodeFile(skinBasePath +"bg/bg.png")));
3. manifest中设置android:sharedUserId,通过这种方式可以达到不同应用相互访问数据的目的,以实现换肤功能。此外需要注意,皮肤apk和应用要有相同签名。
优点:可定期提供换肤包供下载,换肤方式灵活,同时效率比较高。
缺点:如需使用某个皮肤,必须安装该皮肤。但其实现起来还是用代码的方式来提供置换的。同时,让两个工程来共享一个进程,这样做十分的危险。此外,安装了很多皮肤后,应用程序列表里面会有很多皮肤程序。在主程序卸载后,皮肤工程不能同样的卸载。如卸载腾讯微博之后,安装的皮肤apk没有被卸载。
实现技术:该技术的重点是怎样实现一个应用如何读取另一个apk中的资源。
在android系统中,apk之间可以相互读取数据的条件是:有同样的签名,并且manifest中配置的android:sharedUserId属性值相同,那么两个apk运行在同一个进程中,可以互相访问任意数据.注意:只访问资源时不需要添加该属性值。
实现方法:
1)应用程序和皮肤程序的AndroidManifest.xml中配置minifest属性
例如: android:sharedUserId="com.wwwww.skin",值可以是做任意的。
2) 文件与应用apk中对同一功能的皮肤文件名要一致
例如:应用程序的背景图片路径:\WBlogSkin\res\drawable-hdpi\bg.png
则皮肤apk中的背景图片文件路径也应该是SkinApk\res\drawable-hdpi\bg.png
3)访问资源的方法
Context context =createPackageContext("com.wts.skin.night.hdpi",
Context.CONTEXT_IGNORE_SECURITY);
获取到皮肤包包名(com.wts.skin.night.hdpi)对应Context,通过返回的context对象就可以访问com.wts.skin.night.hdpi中的任何资源。
例如:应用apk要获得皮肤apk中的xml资源(mSkin为包名)
Context context = mApp.createPackageContext(mSkin, 0);
mSkinResources = context.getResources();
int configResId = mSkinResources.getIdentifier(CONFIG_XML_NAME,"xml", mSkin);
XmlPullParser xmlpull = mSkinResources.getXml(configResId);
这样就得到了对xml资源的引用,其他资源文件的获取方式也是类似的。
注意:通过当前工程的R.id来调用被调用工程的资源,这是错误的,因为R是自动生成的,两个应用的id是没有办法对应的,所以需要通过getIdentifier来查找。
实现实例:腾讯微博等。
从上面可以看出,只有后两种实现(apk方式和zip方式)可以做到皮肤与应用程序实现分离