編輯:Android資訊
縱觀現在各種Android app,其換膚需求可以歸為
對於第一種來說,目測應該是直接通過本地theme來做的,即所有圖片/顏色的資源都在apk裡面打包了。而對於第二種,則相對復雜一些,由於作為一種線上服務,可能上架新皮膚,且那麼多皮膚包放在apk裡面實在太占體積了,所以皮膚資源會在選擇後再進行下載,也就不能直接使用android的那套theme。
內部資源加載方案和動態下載資源下載兩種。動態下載可以稱為一種黑科技了,因為往往需要hack系統的一些方法,所以在部分機型和新的API上有時候可能有坑,但相對好處則很多
內部資源加載都是通過android本身那套theme來做的,相對業務開發來說工作量更大(需要定義attr和theme),不同方案類似地都是在BaseActivity裡面做setTheme,差別主要在解決以下2個問題的策略:
MultipleTheme
做自定義view是為了在setTheme後會去立即刷新,更新頁面UI對應資源(如TextView替換背景圖和文字顏色),在上述項目中,則是通過對rootView進行遍歷,對所有實現了ColorUiInterface的view/viewgroup進行setTheme操作來實現即使刷新的。
顯然這樣太重了,需要把應用內的各種view/viewgroup進行替換。
手動綁定view和要改變的資源類型
Colorful
這個…我們看看用法吧…
ViewGroupSetter listViewSetter = new ViewGroupSetter(mNewsListView); // 綁定ListView的Item View中的news_title視圖,在換膚時修改它的text_color屬性 listViewSetter.childViewTextColor(R.id.news_title, R.attr.text_color); // 構建Colorful對象來綁定View與屬性的對象關系 mColorful = new Colorful.Builder(this) .backgroundDrawable(R.id.root_view, R.attr.root_view_bg) // 設置view的背景圖片 .backgroundColor(R.id.change_btn, R.attr.btn_bg) // 設置背景色 .textColor(R.id.textview, R.attr.text_color) .setter(listViewSetter) // 手動設置setter .create(); // 設置文本顏色
我就是想換個皮膚,還得在activity裡自己去設置要改變哪個view的什麼屬性,對應哪個attribute?是不是成本太高了?而且activity的邏輯也很容易被弄得亂七八糟。
開源項目可參照 Android-Skin-Loader
即覆蓋application的getResource方法,優先加載本地皮膚包文件夾下的資源包,對於性能問題,可以通過attribute或者資源名稱規范(如需要換膚則用skin_開頭)來優化,從而不對不換膚的資源進行額外開銷。
可以重點關注該項目中的SkinInflaterFactory和SkinManager(實現了自己的getColor、getDrawable方法)。
不過由於Android 5.1源碼裡,getDrawable方法的實現被修改了,所以會導致無法跟膚的問題(其實是loadDrawable被修改了,連參數都改了,類似的內部API大改在5.1上還很多)。
4.4的源碼中Resources.java:
public Drawable getDrawable(int id) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; if (value == null) { value = new TypedValue(); } else { mTmpValue = null; } getValue(id, value, true); } // 實際資源通過loadDrawable方法加載 Drawable res = loadDrawable(value, id); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } return res; } // loadDrawable會去preload的LongSparseArray裡面查找 /*package*/ Drawable loadDrawable(TypedValue value, int id) throws NotFoundException { if (TRACE_FOR_PRELOAD) { // Log only framework resources if ((id >>> 24) == 0x1) { final String name = getResourceName(id); if (name != null) android.util.Log.d("PreloadDrawable", name); } } boolean isColorDrawable = false; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { isColorDrawable = true; } final long key = isColorDrawable ? value.data : (((long) value.assetCookie) << 32) | value.data; Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key); if (dr != null) { return dr; } ... ... return dr; }
而5.1代碼裡Resources.java:
// 可以看到,方法參數裡面加上了Theme public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; if (value == null) { value = new TypedValue(); } else { mTmpValue = null; } getValue(id, value, true); } final Drawable res = loadDrawable(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } return res; } /*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException { if (TRACE_FOR_PRELOAD) { // Log only framework resources if ((id >>> 24) == 0x1) { final String name = getResourceName(id); if (name != null) { Log.d("PreloadDrawable", name); } } } final boolean isColorDrawable; final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches; final long key; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { isColorDrawable = true; caches = mColorDrawableCache; key = value.data; } else { isColorDrawable = false; caches = mDrawableCache; key = (((long) value.assetCookie) << 32) | value.data; } // First, check whether we have a cached version of this drawable // that was inflated against the specified theme. if (!mPreloading) { final Drawable cachedDrawable = getCachedDrawable(caches, key, theme); if (cachedDrawable != null) { return cachedDrawable; } }
方法名字都改了
黑科技方法,直接對Resources進行hack,Resources.java:
// Information about preloaded resources. Note that they are not // protected by a lock, because while preloading in zygote we are all // single-threaded, and after that these are immutable. private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables; private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables = new LongSparseArray<Drawable.ConstantState>(); private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists = new LongSparseArray<ColorStateList>();
直接對Resources裡面的這三個LongSparseArray進行替換,由於apk運行時的資源都是從這三個數組裡面加載的,所以只要采用interceptor模式:
public class DrawablePreloadInterceptor extends LongSparseArray<Drawable.ConstantState>
自己實現一個LongSparseArray,並通過反射set回去,就能實現換膚,具體getDrawable等方法裡是怎麼取preload數組的,可以自己看 Resources 的源碼。
等等,就這麼簡單?,NONO,少年你太天真了,怎麼去加載xml,9patch的padding怎麼更新,怎麼打包/加載自定義的皮膚包,drawable的狀態怎麼刷新,等等。這些都是你需要考慮的,在存在插件的app中,還需要考慮是否會互相覆蓋resource id的問題,進而需要修改apt,把resource id按位放在2個range。
手Q和獨立版QQ空間使用的是這種方案,效果挺好。
盡管動態加載方案比較黑科技,可能因為系統API的更改而出問題,但相對來所
好處有
內部加載方案大同小異,主要解決的都是即時刷新的問題,然而從目前的一些開源項目來看,仍然沒有特別簡便的方案。讓我選的話,我寧願讓界面重新創建,比如重啟activity,或者remove所有view再添加回來。
納德拉掌控微軟之後,微軟開啟了蓋茨開發 Windows 之後最重大的一場技術革命,免費增值、操作系統和應用軟件免費等過去不可思議的政策,都變成了現實。在 4 月
前言 在Android開發中,ImageLoader應該算得上是最重要的開源庫之一,由於項目原因(不能使用開源庫),前段時間自己也是需要實現一個簡單的ImageL
在Android開發中,我們經常會需要在Android界面上彈出一些對話框,比如詢問用戶或者讓用戶選擇。這些功能我們叫它Android Dialog對話框,在我們
如果Android機頂盒能夠支持IOS設備的Airplay協議,實現為Airplay服務器,能夠接收和處理來自IOS設備的多媒體數據(視頻、照片和音樂),將能提高