http://www.cnblogs.com/melaniedeng/archive/2012/05/17/2506869.html
做Android應用的人都知道,要一個apk適用多個不同的手機螢幕是很容易的,就是在項目的res檔案夾下面有多套相關的資源檔。程式啟動並執行時候,Android系統會根據當前裝置的資訊去載入不同檔案夾下的資源檔。但是Android系統是怎麼做到這一點的呢?上網上搜了一下,很少有這方便的介紹,只好自己研究下代碼了。下面是我研究代碼得到的結果(正確性有待確認),在這裡分享一下。
這裡以ICS上在Activity的onCreate()方法裡面調用setContentView(int resourceID)為例,介紹一下系統如何根據我們的id(R.layout.xxxx)找到合適的layout檔案進行解析載入:
如果你的res下面有三種不同的layout:layout, layout-sw480dp和 layout-sw600dp,這裡的sw<N>dp表示這個layout檔案夾下面的布局檔案只有在裝置短邊的最小寬頻為N時才載入。你的裝置是800x480的解析度,那麼這個apk安裝在你的裝置上就會載入 layout-sw480dp裡面的布局檔案。下面是framework的java層調用鏈:
Activity.setContentView(int resourceID) -> PhoneWindow.setContentView(int resourceID) -> LayoutInflater.inflate(int resource, ViewGroup root) -> LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot) -> Resources.getLayout(int id) ->
Resources.loadXmlResourceParser(int id, String type) -> Resources.getValue(int id, TypedValue outValue, boolean resolveRefs) -> AssetManager.getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) -> AssetManager.loadResourceValue(int
ident, short density, TypedValue outValue, boolean resolve)
在上面的掉用鏈中:
1. 最後載入的是哪個xml是由Resources.getValue(int id, TypedValue outValue, boolean resolveRefs)調用完成之後的outValue.string決定的,因為outValue.string的值就是你的資源檔的具體路徑,如:
1) xxx/values/xxx.xml
2) xxx/layout-sw600dp/xxx.xml
2. AssetManager.loadResourceValue()調的是frameworks/base/core/jni/android_util_AssetManager.cpp裡面的native方法, 如何獲得正確的outValue值,在native方法倆面主要有以下幾步:
1) 調用frameworks/base/libs/utils/ResourceTypes.cpp 的ResTable::getResource(),遍曆所有資源檔
2) 在ResTable::getResource()裡面調用ResTable::getEntry()來確定資源檔來自哪個entry,即layout,或者layout-sw<N>dp,由此可見,ResTable::getEntry()是我們這個問題的關鍵
3) 在ResTable::getEntry()裡面:
a) 首先擷取本裝置的configurion資訊,螢幕解析度,螢幕大小,locale,橫豎屏等。
b) 根據得到的本裝置的configurion資訊,過濾掉不適應本裝置的entry,比如裝置是800x480的,那麼超過此解析度的資源(例:layout-sw600dp)就要被過濾掉,實現在frameworks/base/include/utils/ResourceTypes.h中ResTable_config的match函數中
c) 對過濾後的resource進行最佳適配,找到最符合的entry檔案。因為之前已經將不符合的,即大解析度的entry已經被過濾掉了,所以這裡就找剩下的最大的就是最佳適配的。實現在frameworks/base/include/utils/ResourceTypes.h中ResTable_config的isBetterThan()函數中。
3. 我做了一個嘗試,就是想讓800x480解析度的裝置上的應用都載入 layout-sw600dp裡面的資源檔。所以將上面b)步驟的frameworks/base/include/utils/ResourceTypes.h裡面ResTable_config的match函數改動如下:
?
/*if (smallestScreenWidthDp != 0
&& smallestScreenWidthDp > settings.smallestScreenWidthDp){
return false; }*/ if (smallestScreenWidthDp != 0
&& smallestScreenWidthDp > 600) {
return
false ; } |
我將settings.smallestScreenWidthDp強制換成了600,這樣的話,所有比600dp小的(包含600)在內的資源檔在做過濾時就被保留了下來,而c)步驟不做檢查,只找最大的,所以layout-sw600dp就成了系統認為的“最合適”的資源問價了。
將重新編譯frameworks/base/libs/utils/產生的lib庫push到/system/libs下面,再重啟手機,然後啟動上述應用,就可以了看見程式載入的layout-sw600dp的ui了。