Android系統更換主題外觀的實現方法
作者:Liuhua Chen
一、 實現思路
安卓應用在讀取資源時是由AssetManager和Resources兩個類來實現的。Resouce類是先根據ID來找到資原始檔名稱,然後再將該檔名稱交給AssetManager來開啟檔案。我們主題開發的核心思路就是在應用讀取資源時,先去主題包裡讀取資源,若有資源,直接返回主題包的資源,若無資源,直接返回應用本身的資源。
二、 方案實現
a) 修改原始碼Resource.java
private
LoadResourcesCallBack mCallBack;
public interface LoadResourcesCallBack{
ColorStateListloadColorStateList(TypedValue value
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() {
}
public boolean regitsterLoadResourcesCallBack(LoadResourcesCallBackcallBack) {
if (callBack== null || mCallBack != null) {
return false;
}
mCallBack = callBack;
return true;
}
在Resouces類是主要加這個介面,這個介面就是主題改變的關鍵所在。介面LoadResourcesCallBack中的抽象方法,從名子中可以發現,Resources類中也有同名的方法。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