1. 程式人生 > >Android系統更換主題外觀的實現方法

Android系統更換主題外觀的實現方法

作者:Liuhua Chen

一、   實現思路

安卓應用在讀取資源時是由AssetManager和Resources兩個類來實現的。Resouce類是先根據ID來找到資原始檔名稱,然後再將該檔名稱交給AssetManager來開啟檔案。我們主題開發的核心思路就是在應用讀取資源時,先去主題包裡讀取資源,若有資源,直接返回主題包的資源,若無資源,直接返回應用本身的資源。

二、   方案實現

a)   修改原始碼Resource.java

private LoadResourcesCallBack mCallBack;

public interface
LoadResourcesCallBack{
    ColorStateListloadColorStateList(TypedValue value

, int id);

   
Drawable loadDrawable(TypedValue value, int id);

   
XmlResourceParser loadXmlResourceParser(int id, String type);

    int
getDimensionPixelSize(int index);

    int
getDimensionPixelOffset(int index);

    float
getDimension(int index);

   
Integer getInteger(int index);
}

public LoadResourcesCallBack getLoadResourcesCallBack() {
   
return mCallBack;
}

public boolean regitsterLoadResourcesCallBack(LoadResourcesCallBackcallBack) {
   
if (callBack== null || mCallBack != null) {
       
return false;
   
}
    mCallBack = callBack
;
    return true;
}

在Resouces類是主要加這個介面,這個介面就是主題改變的關鍵所在。介面LoadResourcesCallBack中的抽象方法,從名子中可以發現,Resources類中也有同名的方法。LoadResourcesCallBack

中的方法就是在Resources同名方法中呼叫,即Resouces類中在執行這些方法時,會先執行LoadResourcesCallBack實現的方法。

LoadResourcesCallBack抽象方法實現如下:

context.getResources().regitsterLoadResourcesCallBack(new Resources.LoadResourcesCallBack() {
   
@Override
   
public ColorStateListloadColorStateList(TypedValue typedValue, int i) {
      
  if (i == 0) return null;
       
String entryName = context.getResources().getResourceEntryName(i);
       
ColorStateList color = ResourceManagerTPV.getInstance(context).getColorStateList(entryName);
             return
color;
   
}

   
@Override
   
public DrawableloadDrawable(TypedValue typedValue, int i) {
    
   if(i == 0){
           
return null;
       
}
        String entryName =
context.getResources().getResourceEntryName(i);
       
Drawable drawable = ResourceManagerTPV.getInstance(context).getDrawable(entryName);
        return
drawable;
   
}

   
@Override
   
public XmlResourceParserloadXmlResourceParser(int i, String s) {
       
return null;
   
}

   
@Override
   
public int getDimensionPixelSize(int i) {
    
  if(i == 0){
           
return -1;
       
}
        String entryName =
context.getResources().getResourceEntryName(i);
        int
dimensionPixelSize = ResourceManagerTPV.getInstance(context).getDimensionPixelSize(entryName);
        return
dimensionPixelSize;
   
}

   
@Override
   
public int getDimensionPixelOffset(int i) {
     
 if(i == 0){
           
return -1;
       
}
        String entryName =
context.getResources().getResourceEntryName(i);
           return
ResourceManagerTPV.getInstance(context).getDimensionPixelOffset(entryName);
   
}

   
@Override
   
public float getDimension(int i) {
  
     if(i == 0){
           
return -1;
       
}
        String entryName =
context.getResources().getResourceEntryName(i);
        return
ResourceManagerTPV.getInstance(context).getDimen(entryName);
   
}

   
@Override
   
public IntegergetInteger(int i) {
     
 if(i == 0){
           
return null;
       
}
        String entryName =
context.getResources().getResourceEntryName(i);
        return
ResourceManagerTPV.getInstance(context).getInteger(entryName);
   
}
})
;

實現原理:在查詢資源時會根據提供的ID進行查詢,然後通過資源ID查詢資源ID對應的資源名稱,然後獲取當前設定的主題包的Context,然後再由主題包的Context通過資源名稱查詢當前主題包下是否有要查詢的資源,有就返回具體資源的值,如圖片,就返回Drawable資源,沒有就返回Null,Resouce類就會執行自己的方法。

b)   通知更新

實現原理:參考系統語言切換的實現方法。

三、    開發中遇到的問題

a)   開發框架的設計

在方案實行前,已討論過主題的實現方案,剛開始實現方案與臺北TPV的主題實現方案類似,都是先製作一個預設的主題包。但是在製作完主題包後,發現該方案,資源為只讀,不能同時支援多個主題動態切換,且在XML中不能直接引用。後來就參考了TUF的做法,覺得這個方案可行,就按這個方案來實行。

b)   程式碼中讀取color資源,仍是應用本身的資源,不是主題包的資源

在與Launcher和SystemUI除錯時,同樣的方法,在程式碼讀取圖片和Color值時,圖片會讀取到安裝的主題包下的資源,而Color資源沒有讀取到,仍是應用本身設定的值。後來發現是在Resources原始碼中getColor,如下紅色字型程式碼中,還沒有判斷是用哪個資源時,已經直接返回應用的Color資源。最後解決方法,在該段程式碼前進行判斷。

@ColorInt

    public int getColor(@ColorRes int id,@Nullable Theme theme) throws NotFoundException {

      ……..

if (value.type >=TypedValue.TYPE_FIRST_INT

                    && value.type <=TypedValue.TYPE_LAST_INT) {

                mTmpValue = value;

                return value.data;

            } else if (value.type !=TypedValue.TYPE_STRING) {

                throw new NotFoundException(

                        "Resource ID#0x" + Integer.toHexString(id) + " type #0x"

                                + Integer.toHexString(value.type) + " isnot valid");

            }

            mTmpValue = null;

        }

           …….

      final ColorStateList csl =loadColorStateList(value, id, theme);

      ……..

}

c)   SystemUI不會更新

SystemUI是一個很特殊的應用,切換資源時,無法同步切換,只能通過重啟手機才會更新資源,而重啟手機又與UX的設計不一致。最後只能通過廣播通知其更新。

d)  Widget應用無法更新

在與Clock除錯時,同樣的方法,同樣的步驟,在widget中就是不切換資源,最後也只能像SystemUI一樣通過廣播進行更新。只有一個Clock,那時感覺用這種方法,也還好,修改的程式碼不多,也不繁瑣。後來,天氣也需要更新,問題就來了。天氣widget的實現方法與Clock不一樣,而且天氣設定圖片的方法也不一樣。若是天氣也是接收廣播,然後再自己程式碼中更新,修改的程式碼量非常多,且本身天氣應用的邏輯就比較複雜,如此下去不是一個可行的實現方案,不排除以後其他的Widget也需要修改,所以這個方案不能實行,只能另闢方法。

  所以就一直去看看Widget的實現原理,發現Widget都是通過RemoteView來遠端代理的。就去檢視RemoteView的原始碼,

private View inflateView(Contextcontext, RemoteViews rv, ViewGroupparent) {
   
// RemoteViews may be built by an application installedin another
    // user. So build a context thatloads resources from that user but
    // still returns the current usersuserId so settings like data / time formats
    // are loaded without requiring crossuser persmissions.
   
final ContextcontextForResources = getContextForResources(context);
   
Context inflationContext = new ContextWrapper(context){
       
@Override
       
public ResourcesgetResources() {
           
return contextForResources.getResources();
       
}
       
@Override
       
public Resources.ThemegetTheme() {
           
return contextForResources.getTheme();
       
}
       
@Override
       
public StringgetPackageName() {
           
return contextForResources.getPackageName();
       
}
    }
;
   
LayoutInflater inflater = (LayoutInflater)
            context.getSystemService(Context.
LAYOUT_INFLATER_SERVICE);
   
// Clone inflater so we load resources from correctcontext and
    // we don't add a filter to thestatic version returned by getSystemService.
   
inflater = inflater.cloneInContext(inflationContext);
   
inflater.setFilter(this);
    return
inflater.inflate(rv.getLayoutId(), parent, false);
}

    通過這個方法開頭的說明,這個方法就是RemoteView獲取資源的關鍵所在,我們只要在getResources()方法中註冊一下Resources類中自定義的介面。事實證明,確實是如此,修改了此次程式碼後,Clock和天氣都不需要執行任何操作。主題市場只需要統計需要修改的Color和Drawable的名稱。

e)   鎖屏桌布和桌面桌布與效果不一致

桌布的設定,系統有提供一些介面進行設定。該開始就用了系統提供的介面進行設定,發現桌面的桌布不會動,而且顯示得很模糊,鎖屏的桌布模糊效果與桌布不在同一個位置,出現分層。顯然這樣的設定方法是不可行的,詢問了Launcher負責人,具體Launcher的裁剪方法也不是非常清楚。最後自己去下載了Launcher的程式碼來看,設定桌布確實好複雜,非常多個類,各種邏輯,要完全明白,真的有點困難。在這個設定桌布上,也花費了較長時間進行分析。雖然現在也不是完全明白怎麼設定的,但是通過各方面的測試,最終達到了效果。

相關推薦

Android系統更換主題外觀實現方法

作者:Liuhua Chen 一、   實現思路 安卓應用在讀取資源時是由AssetManager和Resources兩個類來實現的。Resouce類是先根據ID來找到資原始檔名稱,然後再將該檔名稱交給AssetManager來開啟檔案。我們主題開發的核心思路就是在應用讀

老男孩教育每日一題-2017年5月12日-磁盤知識點:linux系統中LVM配置實現方法?

邏輯卷管理 磁盤 每日一題 1.題目老男孩教育每日一題-2017年5月12日-磁盤知識點:linux系統中LVM配置實現方法?2.參考答案01:將一個或多個物理分區創建為一個PV# pvcreate /dev/sdb{1,2} Physical volume "/dev/sdb1" success

android 自定義dialog的實現方法

listener params .get animator miss nim style wrap 參數 最近一直在做 java 相關的東西, 雖然一直在看 Android 但感覺有點留於理論,總這樣畢竟不行,寫的多不一定懂得多,但要想懂得多就一定要寫的多,於是今天動手寫了

Android系統匯入burp證書實現抓取資料包

Burpsuit設定代理 瀏覽器設定代理   瀏覽器訪問IP下載burp證書   匯出的證書後綴名為.der,這裡我們更改字尾名為.crt 匯入手機中 複製貼上在我們能記住的目錄 後

Android自定義View的實現方法,帶你一步步深入瞭解View(四)

不知不覺中,帶你一步步深入瞭解View系列的文章已經寫到第四篇了,回顧一下,我們一共學習了LayoutInflater的原理分析、檢視的繪製流程、檢視的狀態及重繪等知識,算是把View中很多重要的知識點都涉及到了。如果你還沒有看過我前面的幾篇文章,建議先去閱讀一下,多瞭解一些

Android自定義View的實現方法 帶你一步步深入瞭解View 四

                不知不覺中,帶你一步步深入瞭解View系列的文章已經寫到第四篇了,回顧一下,我們一共學習了LayoutInflater的原理分析、檢視的繪製流程、檢視的狀態及重繪等知識,算是把View中很多重要的知識點都涉及到了。如果你還沒有看過我前面的幾篇文章,建議先去閱讀一下,多瞭解一些原

Android系統聯絡人全特效實現 下 ,字母表快速滾動

                在上一篇文章中,我和大家一起實現了類似於Android系統聯絡人的分組導航和擠壓動畫功能,不過既然文章名叫做《Android系統聯絡人全特效實現》,那麼沒有快速滾動功能顯然是稱不上"全"的。因此本篇文章我將帶領大家在上篇文章的程式碼基礎上改進,加入快速滾動功能。其實ListVi

Android系統定時開關機實現簡述

本實驗基於Android6.0 一 概述: Android系統的定時開關機的實現分為定時開機和定時關機兩部分,其中定時關機比較容易,因為不需要底層驅動的配合,只需要發特定廣播就可以完成,而定時開機的實現稍微麻煩一些,因為需要底層RTC驅動的配合。 二 定時關機 定時關機實現核

[轉]幾種獲取Android系統記憶體使用狀況的方法

 (一)DDMS 的Heap Dump 1) Data Object:java object. 2) Class Object:object of type Class, e.g. what you'd get from java.lang.String.

Android中Button的onClick實現方法

Android中Button的onClick實現方法大概是這樣的吧! 剛剛是看到有程式碼將一個介面傳給了一個函式(是一個建構函式,沒考證是不是也可以傳給一個普通函式),之後我不懂為什麼,就百度了一下。 發現了這樣一個文章:http://blog.csdn.net/suns

Android系統關機充電動畫實現

<span style="font-size:18px;color:#FF0000;">\mediatek\platform\mt6572\lk\mt_logo.c </span> /********** show_animationm_ver:

Android App切換主題實現原理剖析

今天再給大家帶來一篇乾貨。 Android的主題換膚 ,可外掛化提供面板包,無需Activity的重啟直接實現無縫切換,可高仿網易雲音樂的主題換膚。 這個連結是本次的Demo打包出來的樣本SkinChangeDemo,可以去下載下來先試試效果,面板檔案需放到儲存卡的根目錄下。 關於Android的主

替換Android系統映象system.img的方法

之前修改了Android的系統原始碼的framework層程式碼,定製ROM。通過make之後會生成三個映象檔案userdata.img、system.img、ramdisk.img三個檔案。這個時候

Android系統截圖的實現(附程式碼)

1.背景           寫部落格快兩年了,寫了100+的文章,最火的文章也是大家最關注的就是如何實現android系統截圖。其實我們google android_screen_shot就會找到很對

Android提高啟動速度的實現方法

原文地址:http://www.eoeandroid.com/thread-29953-1-1.html Android重量級開發之--提高android啟動速度研究      大家都知道啟動速度慢是智慧作業系統的一個通病,Android也不例外,啟

Android開發之Tween動畫實現方法

1,   Activity中實現 Animation的子類              Animation scaleAnimation = new ScaleAnimation(0f, 1f, 0f, 1f,             Animation.RELATIVE_

定製Android系統開發之八——實現從JNI到Java的回撥

前面已經實現了APP->xxxManager->xxxManagerService->jni的函式呼叫,這篇博文就來實現jni->xxxManagerService的回撥。 使用環境 我先說一下我的應用環境吧。我在有一個對裝置節點

android 系統獲取通話狀態的方法

獲取電話通相關狀態方法及說明: 1>編寫一個監聽器類,該類繼承自PhoneStateListener:  重寫該類中的監聽方法:    classMyPhoneListener extends PhoneStateListener{       /**        

Android系統手機端抓包方法(tcpdump)

抓包準備 1. Android手機需要先獲得root許可權。一種是否獲得root許可權的檢驗方法:安裝並開啟終端模擬器(可通過安卓市場等渠道獲得)。在終端模擬器介面輸入su並回車,若報錯則說明未root,若命令提示符從$變#則為rooted; 2. 如果Android手機尚未root,可通過super

Android系統篇之----免root實現Hook系統服務攔截方法

一、Binder機制回顧 在之前一篇文章中介紹了 Android中的Binder機制和系統遠端服務呼叫機制,本文將繼續借助上一篇的內容來實現Hook系統服務攔截指定方法的邏輯,瞭解了上一篇文章之後,知道系統的服務其實都是一個遠端Binder物件,而這個物件都是由Se