android换皮肤思路总结

来源:互联网 发布:绿城和万科哪个好 知乎 编辑:程序博客网 时间:2024/04/29 16:24

   前段时间公司有做换皮肤的项目,经过网上搜罗,查看资料,我个人总结三种换皮肤的方法。

    网上说的最多的就是使用android:sharedUserId标签来共享资源,但是经我测试无论用不用这个标签资源都可以访问,而且Launcher换皮肤的时候不能用这个标签来共享进程。

第一种方法先上代码:

MainActivity.java

package com.app;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;import android.os.Bundle;import android.app.Activity;import android.content.Context;import android.content.pm.PackageManager.NameNotFoundException;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;public class MainActivity extends Activity {private Context skinContext;private Map<String,Map<String, Object>> resMap;private Button change;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);try {skinContext = this.createPackageContext("com.skin", CONTEXT_IGNORE_SECURITY|CONTEXT_INCLUDE_CODE);} catch (NameNotFoundException e) {// TODO Auto-generated catch blockskinContext=null;e.printStackTrace();}change = (Button) findViewById(R.id.button1);change.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubloadSkinRes();View view = getLayoutFromSkin("skin_main");if(view !=null)setContentView(view);}});}private void loadSkinRes(){if(skinContext != null){resMap = getSkinResourcesId("com.skin");}else{resMap=null;}}/** * 获取皮肤包中的layout * 并转化为VIEW * @param layoutName * @return */private View getLayoutFromSkin(String layoutName){View view;if(resMap == null)return null;Map<String, Object> temp = resMap.get("layout");int viewId = (Integer) temp.get(layoutName);if(viewId != 0){//引用皮肤包资源转化万恶哦ViewLayoutInflater inflater =LayoutInflater.from(skinContext);view = inflater.inflate(skinContext.getResources().getLayout(viewId), null);}else{view = null;}return view;}/** * 取得对应包的所有资源的ID * 存在MAP中 * @param packageName * @return */private Map<String,Map<String, Object>> getSkinResourcesId(String packageName){Map<String, Object> temp =  null;Map<String,Map<String, Object>> resMap =new HashMap<String,Map<String,Object>>();try {//取得皮肤包中的R文件Class<?> rClass = skinContext.getClassLoader().loadClass(packageName+".R");//取得记录各种资源的ID的类Class<?>[] resClass =rClass.getClasses();String className,resourceName;int resourceId=0;for(int i=0;i<resClass.length;i++){className = resClass[i].getName();//取得该类的资源Field field[] = resClass[i].getFields();for(int j =0;j < field.length; j++){resourceName = field[j].getName();try {resourceId = field[j].getInt(resourceName);} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}if(resourceName!=null && !resourceName.equals("")){temp =new HashMap<String, Object>();temp.put(resourceName, resourceId);Log.i("DDDDD", "className:"+className+"  resourceName:"+resourceName+"  " +"resourceId:"+Integer.toHexString(resourceId));}}//由于内部类的关系className应该是com.skin.R$layout的形式//截掉前面的包名和.R$以方便使用className = className.substring(packageName.length()+3);resMap.put(className, temp);}} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}return resMap;}}

上面代码中只写了访问layout的方法,其他的资源也可以用类似的方法访问到。

activity_main.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".MainActivity" >    <TextView        android:id="@+id/textView1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="@string/thisres" />    <Button        android:id="@+id/button1"        style="?android:attr/buttonStyleSmall"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignRight="@+id/textView1"        android:layout_below="@+id/textView1"        android:layout_marginTop="44dp"        android:text="换皮肤" /></RelativeLayout>

以下为皮肤包中的布局skin_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".MainActivity" >    <TextView        android:id="@+id/textView1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="@string/hello_world" />    <ImageView        android:id="@+id/imageView1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignLeft="@+id/textView1"        android:layout_below="@+id/textView1"        android:layout_marginTop="32dp"        android:src="@drawable/ic_launcher" /></RelativeLayout>

在不用android:sharedUserId标签的时候也可以换成皮肤包的布局。

程序运行后:

点击“换皮肤”后


其实这种反射机制的换皮肤思路android提供能更强大的api,这也是我说的第二种方法:

/**     *  换皮肤 获取各种资源     * @param skinPackageContext     * @param resourceType     * @param resourceName     * @param packgeName     * @return     */    private int getResourcesFromSkin(Context skinPackageContext,String resourceType,    String resourceName,String packgeName)    {    int id=0;    try    {    id=skinPackageContext.getResources().getIdentifier(resourceName,        resourceType, packgeName);    }    catch(Exception e)    {    id=0;    }    return id;    }    /**     *  取得Raw中的xml     * @param skinPackageContext     * @param resourceName     * @return     */    public InputStream getXmlInRawFromSkin(Context skinPackageContext,String resourceName)    {        int id=getResourcesFromSkin(skinPackageContext,"raw",    resourceName,"com.flyaudio.skin");    if(id==0)    {    id=getgetResourcesFromSkin(this,"raw",resourceName,"com.android.launcher");    return this.getResources().openRawResource(id);    }    return skinPackageContext.getResources().openRawResource(id);    }    /**     * 取得bool值     * @param skinPackageContext     * @param resourceName     * @return     */    public boolean getBoolFromSkin(Context skinPackageContext,String resourceName)    {    int id=getResourcesFromSkin(skinPackageContext,"bool",    resourceName,"com.flyaudio.skin");    if(id==0)    {    id=getgetResourcesFromSkin(this,"bool",resourceName,"com.android.launcher");    if(id!=0)    return this.getResources().getBoolean(id);    }    else    return skinPackageContext.getResources().getBoolean(id);    return false;    }    /**     *  取得皮肤包中的字符串     * @param skinPackageContext     * @param resourceName     * @return     */    public String getStringFromSkin(Context skinPackageContext,String resourceName)    {    int id=getResourcesFromSkin(skinPackageContext,"string",    resourceName,"com.flyaudio.skin");    if(id==0)    {    id=getResourcesFromSkin(this,"string",resourceName,"com.android.launcher");    if(id!=0)    return this.getResources().getString(id);    }    else    return skinPackageContext.getResources().getString(id);    return null;    }    /**     *  取得皮肤包中整型参数     * @param skinPackageContext     * @param resourceName     * @return     */    public Integer getIntegerFromSkin(Context skinPackageContext,String resourceName)    {    int id=getResourcesFromSkin(skinPackageContext,"integer",    resourceName,"com.flyaudio.skin");    if(id==0)    {    id=getgetResourcesFromSkin(this,"integer",resourceName,"com.android.launcher");    return this.getResources().getInteger(id);    }    return skinPackageContext.getResources().getInteger(id);    }    /**     *  取得皮肤包中Drawable资源     * @param skinPackageContext     * @param resourceName     * @return     */    public Drawable getDrawableFromSkin(Context skinPackageContext,String resourceName)    {    int id =getResourcesFromSkin(skinPackageContext,"drawable",    resourceName,"com.flyaudio.skin");    if(id==0)    {    id=getResourcesFromSkin(this,"drawable",resourceName,"com.android.launcher");    return this.getResources().getDrawable(id);    }    return skinPackageContext.getResources().getDrawable(id);    }    /**     *  取得皮肤包中color资源     * @param skinPackageContext     * @param resourceName     * @return     */    public int getColorFromSkin(Context skinPackageContext,String resourceName)    {    int id =getResourcesFromSkin(skinPackageContext,"color",    resourceName,"com.flyaudio.skin");    if(id==0)    {    id=getResourcesFromSkin(this,"color",resourceName,"com.android.launcher");    return this.getResources().getColor(id);    }    return skinPackageContext.getResources().getColor(id);    }        /**     *  取得皮肤包中layout资源     * @param skinPackageContext     * @param resourceName     * @return     */    public View getLayoutFromSkin(Context skinPackageContext,String resourceName)    {    int viewId=getResourcesFromSkin(skinPackageContext,"layout",    resourceName,"com.flyaudio.skin");    LayoutInflater inflater=null;    View view=null;    if(viewId !=0)    {    inflater = LayoutInflater.from(skinPackageContext);    view =inflater.inflate(skinPackageContext.getResources().getLayout(viewId), null);    }    else    {    viewId=getgetResourcesFromSkin(this,"layout",resourceName,"com.android.launcher");    inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);        view =inflater.inflate(getResources().getLayout(viewId), null);    }    return view;    }    /**     * 取得皮肤包中ID     * @param skinPackageContext     * @param resourceName     * @return     */    public int getIdFromSkin(Context skinPackageContext,String resourceName,String type)    {    int id=0;    id=getResourcesFromSkin(skinPackageContext,type,    resourceName,"com.flyaudio.skin");    if(id==0)    {    id=getResourcesFromSkin(this,type,resourceName,"com.android.launcher");    }    return id;    }

以上代码是我在做Launcher换皮肤的时候使用的一些方法,android提供的这个api其实在Launcher程序的壁纸部分也有用到。

上面2中方法要求资源名字是一样的,资源数量可以不一样,第一种方法因为我们已经把整个资源包的资源ID都记录到Map里面了,而第二种的话,我没去详细了解getIdentifier()的处理机制,有知道的同学,可以留言讨论。

第三种方法就是修改frameworks,一个程序运行用到的一般都会有2套资源,一是android 自带的,也就是访问的时候我们用的android:开头的资源,二当然是我们自己的程序的资源。既然我们可以访问android的资源,我们当然也可以访问除了android资源和程序本身的资源以外的资源。

在 frameworks/base/core/java/android/app/ActivityThread.java中有对资源访问的描述。

其实getResources方法返回的结果就是在这里定义的,mResources的赋值代码为:

mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
                    Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);

我们可以在这里修改取得资源的优先级,让程序优先读取皮肤包的资源,这里就不多做解释。

详细请见:http://blog.csdn.net/luoshengyang/article/details/8791064


总结:

 以上我所知的三种方法均测试成功过,但不保证绝对正确。也欢迎各位同学指正,转载请注明出处。

 另附上方法一的程序:

传送门:http://download.csdn.net/detail/tangnengwu/7136273





0 0