前段時間公司有做換皮膚的項目,經過網上搜羅,查看資料,我個人總結三種換皮膚的方法。網上說的最多的就是使用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; @Override protected 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 block skinContext=null; e.printStackTrace(); } change = (Button) findViewById(R.id.button1); change.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub loadSkinRes(); 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) { //引用皮膚包資源轉化萬惡哦View LayoutInflater 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 block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.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 block e.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