Android Apk换肤

一、换肤种类的功能划分

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的方式进行更换资源文件以及布局

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值