[천장] 안드로이드 스킨 교체(테마) 용이

8994 단어
현재 많은 앱들이 스킨케어 기능을 가지고 사용자 자신의 취향에 따라 자신의 인터페이스를 맞춤형으로 만들 수 있다. 예를 들어 시나닷컴, 왕이뉴스 등이다.오늘 여기에서 나는 앱의 스킨케어를 실현하는 메커니즘을 소개하고자 한다.
제가 몇 가지 앱 스킨케어 앱을 찾았는데 스킨케어는 기본적으로 인터페이스의 Icon, 배경 이미지, 배경색 등을 바꿨습니다. 기본적으로 레이아웃을 바꾸는 것을 만나지 못했습니다. 사실 레이아웃도 바꿀 수 있지만 필요 없다고 생각합니다.그래서 이 글에서 설명한 스킨케어도 아이콘, 배경그림 등 자원을 바꾸는 것을 가리킨다.
인터넷 검색을 통해 나는 인터넷에 대략 이렇게 집중적인 스킨케어 메커니즘을 제공한 것을 발견했다.
1. 피부팩을 apk에 직접 넣는 방안은 매우 간단하지만 유연성이 부족하고 apk를 크게 만든다.
2. 피부를 하나의 독립된 apk 파일로 만들고 프로젝트 프로젝트와shareUsedId를 공용하며 같은 서명을 가진다.이런 방안은 첫 번째 방안에 비해 유연성이 비교적 크고 단점은 사용자의 설치가 필요하다는 것이다. 시나닷컴이 현재 사용하고 있는 것이 바로 이런 방안이다.
내가 오늘 소개하고자 하는 이런 방안은 두 번째 방안과 비교적 유사하지만 나의 자원 패키지는 설치하지 마라. 왜냐하면 사용자들은 일반적으로 엉망진창인 응용을 설치하기를 원하기 때문이다.
이 글을 배우기 전에 제 전 글인 안드로이드 자원 관리 메커니즘 분석을 배우는 것이 좋습니다. 피부 관리는 사실 자원 관리이기 때문입니다.이제 피부를 바꾸는 법을 배워보도록 하겠습니다.
1. 우선 우리는 스킨백을 준비해야 한다. 이 스킨백에는 어떠한 Activity도 포함되지 않고 그 안에 자원 파일만 있다. 여기에 나는 간단하게 하기 위해 하나의 color만 추가한다.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="main_btn_color">#E61ABD</color>
    <color name="main_background">#38F709</color>
    
    <color name="second_btn_color">#000000</color>
    <color name="second_background">#FFFFFF</color>
    
</resources>

2. 이 자원을 apk 파일로 포장하여 sd카드에 넣는다(실제 항목은 내 네트워크에서 다운로드할 수 있다)
3. 스킨케어가 필요한Activity를 ISkinUpdate(이것은 스스로 이름을 정의할 수 있음) 인터페이스로 구현
public class MainActivity extends Activity implements ISkinUpdate,OnClickListener
{
	private Button btn_main;
	private View main_view;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	  this.setContentView(R.layout.activity_main);
		
		SkinApplication.getInstance().mActivitys.add(this);
		btn_main=(Button)this.findViewById(R.id.btn_main);
    btn_main.setOnClickListener(this);
		
		main_view=this.findViewById(R.id.main_view);
		
		
		
		
	}
    	
    @Override
    protected void onResume() {
      super.onResume();
      if(SkinPackageManager.getInstance(this).mResources!=null)
      {
        updateTheme();
        Log.d("yzy", "onResume-->updateTheme");
      }
    }

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			//Toast.makeText(this, "change skin", 1000).show();
			File dir=new File(Environment.getExternalStorageDirectory(),"plugins");
			
			File skin=new File(dir,"SkinPlugin.apk");
			if(skin.exists())
			{
				  SkinPackageManager.getInstance(MainActivity.this).loadSkinAsync(skin.getAbsolutePath(), new loadSkinCallBack() {
          @Override
          public void startloadSkin() 
          {
            Log.d("yzy", "startloadSkin");
          }
          
          @Override
          public void loadSkinSuccess() {
            Log.d("yzy", "loadSkinSuccess");
            MainActivity.this.sendBroadcast(new Intent(SkinBroadCastReceiver.SKIN_ACTION));
          }
          
          @Override
          public void loadSkinFail() {
            Log.d("yzy", "loadSkinFail");
          }
        });
			}
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	public void updateTheme() 
	{
		// TODO Auto-generated method stub
		if(btn_main!=null)
		{
			try {
				Resources mResource=SkinPackageManager.getInstance(this).mResources;
				Log.d("yzy", "start and mResource is null-->"+(mResource==null));
				int id1=mResource.getIdentifier("main_btn_color", "color", "com.skin.plugin");
				btn_main.setBackgroundColor(mResource.getColor(id1));
				int id2=mResource.getIdentifier("main_background", "color","com.skin.plugin");
				main_view.setBackgroundColor(mResource.getColor(id2));
				//img_skin.setImageDrawable(mResource.getDrawable(mResource.getIdentifier("skin", "drawable","com.skin.plugin")));
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		SkinApplication.getInstance().mActivitys.remove(this);
		super.onDestroy();
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		if(v.getId()==R.id.btn_main)
		{
			Intent intent=new Intent(this,SecondActivity.class);
			this.startActivity(intent);
		}
	}
}

이 코드는 주로 onOptions Item Selected를 보는데 이 방법에서 자원 apk 경로를 통해 이 자원 apk가 Resources 대상에 대응하는 것을 얻을 수 있다.SkinPacakge Manager에서 뭘 했는지 바로 만나보도록 하겠습니다.
/**
 *        
 * com.skin.demo.SkinPackageManager
 * @author yuanzeyao <br/>
 * create at 2015 1 3    3:24:16
 */
public class SkinPackageManager 
{
  private static SkinPackageManager mInstance;
  private Context mContext;
  /**
   *       
   */
  public String mPackageName;
  
  /**
   *     
   */
  public Resources mResources;
  
  private SkinPackageManager(Context mContext)
  {
    this.mContext=mContext;
  }
  
  public static SkinPackageManager getInstance(Context mContext)
  {
    if(mInstance==null)
    {
      mInstance=new SkinPackageManager(mContext);
    }
    
    return mInstance;
  }
  
  
  /**
   *         
   * @param dexPath
   *                 
   * @param callback
   *            
   */
  public void loadSkinAsync(String dexPath,final loadSkinCallBack callback)
  {
    new AsyncTask<String,Void,Resources>()
    {

      protected void onPreExecute() 
      {
        if(callback!=null)
        {
          callback.startloadSkin();
        }
      };
   
      @Override
      protected Resources doInBackground(String... params) 
      {
        try {
          if(params.length==1)
          {
            String dexPath_tmp=params[0];
            PackageManager mPm=mContext.getPackageManager();
            PackageInfo mInfo=mPm.getPackageArchiveInfo(dexPath_tmp,PackageManager.GET_ACTIVITIES);
            mPackageName=mInfo.packageName;
            
            
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath_tmp);
            
            Resources superRes = mContext.getResources();
            Resources skinResource=new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
            SkinConfig.getInstance(mContext).setSkinResourcePath(dexPath_tmp);
            return skinResource;
          }
          return null;
        } catch (Exception e) {
          return null;
        } 
        
      };
      
      protected void onPostExecute(Resources result) 
      {
        mResources=result;
       
        if(callback!=null)
        {
          if(mResources!=null)
          {
            callback.loadSkinSuccess();
          }else
          {
            callback.loadSkinFail();
          }
        }
      };
      
    }.execute(dexPath);
  }
  
  /**
   *          
   * com.skin.demo.loadSkinCallBack
   * @author yuanzeyao <br/>
   * create at 2015 1 4    1:45:48
   */
  public static interface loadSkinCallBack
  {
    public void startloadSkin();
    
    public void loadSkinSuccess();
    
    public void loadSkinFail();
  }
  
  
 
}
loadSkin Async를 호출한 후 성공하면 스킨케어 방송을 보내고 현재 피부 apk의 경로를 sp에 저장하여 다음 앱을 시작하면 이 피부 자원을 직접 불러올 수 있도록 합니다.
스킨케어 수락 방송은 스킨케어에 등록된 것으로 이 방송을 받은 후 이미 시작되고 스킨케어가 필요한 Activity의 업데이트 Theme 방법을 모두 호출하여 스킨케어를 실현한다.
public class SkinApplication extends Application 
{
	private static SkinApplication mInstance=null;
	
	public ArrayList<ISkinUpdate> mActivitys=new ArrayList<ISkinUpdate>();
	
	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		mInstance=this;
		String skinPath=SkinConfig.getInstance(this).getSkinResourcePath();
		if(!TextUtils.isEmpty(skinPath))
		{
		  //       ,        ,       
		  SkinPackageManager.getInstance(this).loadSkinAsync(skinPath, null);
		}
		
		SkinBroadCastReceiver.registerBroadCastReceiver(this);
	}
	
	public static SkinApplication getInstance()
	{
		return mInstance;
	}
	
	@Override
	public void onTerminate() {
		// TODO Auto-generated method stub
		SkinBroadCastReceiver.unregisterBroadCastReceiver(this);
		super.onTerminate();
	}
	
	public void changeSkin()
	{
		for(ISkinUpdate skin:mActivitys)
		{
			skin.updateTheme();
		}
	}
}

여기서 스킨케어는 아이콘, 배경색 등만 바꾸는 것이기 때문에 비교적 간단합니다. 만약에 레이아웃 파일을 바꾸려면 좀 복잡해야 합니다. 여기는 더 이상 소개하지 않겠습니다. 관심 있는 사람은 스스로 연구할 수 있습니다...

좋은 웹페이지 즐겨찾기