1. 程式人生 > >android換面板思路總結

android換面板思路總結

   前段時間公司有做換面板的專案,經過網上搜羅,檢視資料,我個人總結三種換面板的方法。

    網上說的最多的就是使用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

總結:

 以上我所知的三種方法均測試成功過,但不保證絕對正確。也歡迎各位同學指正,轉載請註明出處。

 另附上方法一的程式: