Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android AssetManager (1)

Android AssetManager (1)

編輯:關於Android編程

AssetManager是android的資源管理器,負責管理android系統所有的資源.資源可以分系統級別和應用級別.

系統級別主要是framework-res.apk,即編譯framework/base/core/res目錄下的,當然有時候定制系統會有定制的資源,一般放在~/vendor/overlay/...下面,可以在framework/base/core/res的android.mk中包含vendor/overlay/framework下面的文件資源,這樣可以生成一個總的framework-res.apk出來,也可以分開生成兩個,一個是系統原生的framework-res.apk,一個可以是定制的-res.apk,如果是分開的,那麼就需要修改framework裡面的程序,將定制的res添加到路徑中,不然系統或者應用仍然無法查找.

另外就是framework/base/data/下面的資源,一般是字體,系統聲音,以及自帶媒體視頻.定制系統經常會遇到調整這些什麼系統字體,聲音,或者自帶媒體視頻之類的,從Nokia時代就開始了.

應用級別主要是開發人員在app工程中res目錄下的各種資源以及assets目錄.

上面的資源共同的特點就是需要一個配置信息Configuration : 包括國家語言,時區,屏幕密度(以及屏幕大小)等等,這樣的區分就可以在不同的地區使用不同的資源,不同設備使用不同資源---顯而易見,屏幕適配.iphone app不需要做特別的適配!

 

資源的加載是從創建(或者說運行APP的第一個Activity開始).ActivityThread創建Activity對象,Activity的父類ContextImpl中的init開始初始化資源加載等操作.

下面從ContextImpl的init開始:

final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread,
            Resources container, String basePackageName, UserHandle user) {
        mPackageInfo = packageInfo;
        mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
        mResources = mPackageInfo.getResources(mainThread);

        if (mResources != null && container != null
                && container.getCompatibilityInfo().applicationScale !=
                        mResources.getCompatibilityInfo().applicationScale) {
            if (DEBUG) {
                Log.d(TAG, "loaded context has different scaling. Using container's" +
                        " compatiblity info:" + container.getDisplayMetrics());
            }
            mResources = mainThread.getTopLevelResources(
                    mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY,
                    null, container.getCompatibilityInfo());
        }
        mMainThread = mainThread;
        mActivityToken = activityToken;
        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
        mUser = user;
    }

資源的對象Resource的獲取:

mResources = mainThread.getTopLevelResources(
                    mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY,
                    null, container.getCompatibilityInfo());

跳轉到ActivityThread類中:

Resources getTopLevelResources(String resDir,
            int displayId, Configuration overrideConfiguration,
            CompatibilityInfo compInfo) {

在這個方法中:

WeakReference wr = mActiveResources.get(key);
            r = wr != null ? wr.get() : null;
            //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
            if (r != null && r.getAssets().isUpToDate()) {
                if (false) {
                    Slog.w(TAG, "Returning cached resources " + r + " " + resDir
                            + ": appScale=" + r.getCompatibilityInfo().applicationScale);
                }
                return r;
            }

ActivityThread通過一個Hashmap的結構體mActiveResources來維持APP和其對應的資源Resource.通過r.getAssets().isUpToDate來判斷是否存在對應的資源並且資源沒有outtime(不過這個outtime不知道為什麼要檢查,暫時不清楚),一般APP都會有對應的,返回true,那麼就會返回獲取到的r.

但是如果沒有找到與上面resDir相符的,那麼程序就會創建AssetManager並且加載資源:

AssetManager assets = new AssetManager();
        if (assets.addAssetPath(resDir) == 0) {
            return null;
        }

方法:

addAssetPath

非常重要,在很多場所都用到,但是這個方法卻是標記@hide的,所以一般如果要使用都是通過反射的方式,比如:如果放一張圖片到assets文件夾中,那麼不會產生R.id,那麼就需要開發人員通過上面的方法加載到資源路徑下去,才能夠提供給其他的使用,常見的比如資源插件,資源在另外一個plugin中,那麼這個plugin中的所有資源就需要先添加到資源路徑下,才能夠被宿主機使用.這個就廢話不多說了,記住就好了.

public native final int addAssetPath(String path);

    /**
     * Add multiple sets of assets to the asset manager at once.  See
     * {@link #addAssetPath(String)} for more information.  Returns array of
     * cookies for each added asset with 0 indicating failure, or null if
     * the input array of paths is null.
     * {@hide}
     */
    public final int[] addAssetPaths(String[] paths) {
        if (paths == null) {
            return null;
        }

        int[] cookies = new int[paths.length];
        for (int i = 0; i < paths.length; i++) {
            cookies[i] = addAssetPath(paths[i]);
        }

        return cookies;
    }

很顯然這個方法是一個native方法:最終是在AssetManager.cpp

bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
    AutoMutex _l(mLock);

    asset_path ap;

在這個函數中,路徑被添加到一個Vector的向量表中:

// Skip if we have it already.
    for (size_t i=0; i

下面還有一段程序,當加載的是framework-res.apk的包,系統還會繼續檢查是否有替換資源,即覆蓋資源---覆蓋機制:

// add overlay packages for /system/framework; apps are handled by the
    // (Java) package manager
    if (strncmp(path.string(), "/system/framework/", 18) == 0) {
        // When there is an environment variable for /vendor, this
        // should be changed to something similar to how ANDROID_ROOT
        // and ANDROID_DATA are used in this file.
        String8 overlayPath("/vendor/overlay/framework/");
        overlayPath.append(path.getPathLeaf());
        if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {
            asset_path oap;
            oap.path = overlayPath;
            oap.type = ::getFileType(overlayPath.string());
            bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
            if (addOverlay) {
                oap.idmap = idmapPathForPackagePath(overlayPath);

                if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
                    addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
                }
            }
            if (addOverlay) {
                mAssetPaths.add(oap);
            } else {
                ALOGW("failed to add overlay package %s\n", overlayPath.string());
            }
        }
    }

這個在前言說了,會去看看vendor/overlay/framework下是否有替換的資源.這裡是系統的資源覆蓋機制.不過我搜索了一下,網上還有如下:

如果手機廠商要利用上述的資源覆蓋機制來自定義自己的系統資源,那麼還需要提供一個idmap文件,用來說明它在/vendor/overlay/framework/目錄提供的Apk文件要覆蓋系統的哪些默認資源,使用資源ID來描述,因此,這個idmap文件實際上就是一個資源ID映射文件。這個idmap文件最終保存在/data/resource-cache/目錄下,並且按照一定的格式來命令,例如,假設手機廠商提供的覆蓋資源文件為/vendor/overlay/framework/framework-res.apk,那麼對應的idmap文件就會以名稱為@vendor@overlay@[email protected]@idmap的形式保存在目錄/data/resource-cache/下。

        關於Android系統的資源覆蓋(Overlay)機制,可以參考frameworks/base/libs/utils目錄下的READ文件

從這段話看出,網上面替換是將vendor/overlay/framework/的資源生成一個新的res.apk來提供替換方案.其實沒看太明白,而且READ文件也沒有找到.而且所謂的提供idmap文件看起來很高大上,剛開始我還以為需要人工去生成,有一點的茫然.我後來這個地方打印log出來:

\

然後將機器重啟:

\

加載framework-res.apk的時候,上面的判斷成立.再進一步打出overlaypath:

\

如果可以改程序,那麼其實vendor.../可以改為任意目錄,讓系統根據任意的目錄查找就好了.

至於後面要提供什麼idmap,我是迷糊了,如果overlayPath有效,執行下面的就有idmap了,至於什麼需要提供什麼,以及什麼格式之類的,有點嚇人,這些都是下面的函數搞定並返回idmap

 

oap.idmap = idmapPathForPackagePath(overlayPath);

 

 

if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
                    addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
                }

 

然後通過下面將overlay資源添加到資源路徑下去.

 

if (addOverlay) {
                mAssetPaths.add(oap);
            } 

 

 

如果是APP呢,那麼上面的程序函數前面那個注釋就給了提示,如果是app的就可以到PackageManager中的getResourcesForApplication方法,但是這個方式是個抽象方法,實際要看ApplicationPackageManager中的:

@Override public Resources getResourcesForApplication(
        ApplicationInfo app) throws NameNotFoundException {
        if (app.packageName.equals("system")) {
            return mContext.mMainThread.getSystemContext().getResources();
        }
        Resources r = mContext.mMainThread.getTopLevelResources(
                app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,
                        Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);
        if (r != null) {
            return r;
        }
        throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
    }

我在這個地方打印log出來:

 

\

 

這個一看就顯而易見了,如果是定制系統就可以做一個隱射表,比如上面是桌面Launcher的APK,如果要整體更換桌面的主題,那麼就可以再做一個launcher_plugin.apk放到系統裡面,但是這兩個工程的資源要一一對應,比如launcher有一個R.id.pumpkin的資源,那麼launcher_plugin.apk必須要也有,因為這是整體替換.這樣如果上面的app.sourceDir等於桌面的時候,就將這個app.sourceDir的值替換為launcher_plugin.apk中,那麼launcher就會使用這個插件資源了.這個是整體工程替換.

題外話:我記得我以前公司如果定制系統資源都是在vendor/overlay/framework下去增加新的資源,而不是替換的方式,相當於做了一套資源(主題資源等),然後讓團隊所有的APP使用公司定制的資源,而不是替換的方式,這樣就有一個問題,定制資源改了,上面所有的APP也要對應著改;但是有一個好處就是保留了原生資源,這樣其他的APP的主題就不會影響,但是如果是替換的方式,那麼就會影響第三方的APP,因為那個時候第三方的使用系統資源部分的都將被OEM定制了,也就是說運行在不同手機上APP視覺效果不一樣了.

 

下篇在看看如何獲取單個資源ID信息.

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved