Android中资源加载源码分析

来源:互联网 发布:淘宝类目 编辑:程序博客网 时间:2024/05/16 04:19

1.简介

我们在布局文件中使用View的一些属性时,有没有想过是怎么加载进来的? 比如说在布局文件中使用ImageView设置图片时;
<ImageView        android:id="@+id/iv_skin"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:src="@drawable/skin1" />

下面我们尝试着去源码里边寻找答案;

2.扒源码分析,资源是如何加载的

这里我们还是以最常用的ImageView为例,查看src属性是怎么加载进来的:先查看ImageView.java源码(6.0源码为例)

public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        initImageView();        // 部分代码如下        final TypedArray a = context.obtainStyledAttributes(                attrs, com.android.internal.R.styleable.ImageView, defStyleAttr, defStyleRes);        // 通过TypedArray获取图片drawable        Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);        if (d != null) {            setImageDrawable(d);  // 设置到imageView上        }        ...省略代码...}

下面追踪 TypedArray.java的getDrawable(int index)方法

    @Nullable    public Drawable getDrawable(int index) {        if (mRecycled) {            throw new RuntimeException("Cannot make calls to a recycled instance!");        }        final TypedValue value = mValue;        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {            if (value.type == TypedValue.TYPE_ATTRIBUTE) {                throw new UnsupportedOperationException(                        "Failed to resolve attribute at index " + index + ": " + value);            }            //我们可以看到是通过Resources类来加载drawable图片的--加载资源的方式            return mResources.loadDrawable(value, value.resourceId, mTheme);        }        return null;    }

其实多看几个View的资源加载过程,都是通过Resources类来加载资源的

3.源码中Resources对象的创建过程分析

我们在activity中也经常这样使用 getResources().getDrawable(R.drawable.account)那么这个Resources实例是如何创建的呢?
我们点进去追踪 ContextThemeWrapper.java 的

` @Overridepublic Resources getResources() {    if (mResources != null) {        return mResources;    }    if (mOverrideConfiguration == null) {        mResources = super.getResources();        return mResources;    } else {        Context resc = createConfigurationContext(mOverrideConfiguration);        mResources = resc.getResources();        return mResources;    }}`

我们接着看 super.getResources()调用,发现

`ContextWrapper.java类@Overridepublic Resources getResources(){    return mBase.getResources();}`

继续追踪,最终发现是Context.java的抽象方法
public abstract Resources getResources();
我们只能来找Context的实现类了,那么我们从ContextImpl.java这个实现类入手(android.app包下的),查找mResources = 在那里赋值的

    private ContextImpl(ContextImpl container, ActivityThread mainThread,            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,            Display display, Configuration overrideConfiguration, int createDisplayWithId) {        ...省略代码,我们只看我们关心的代码...        mPackageInfo = packageInfo;        mResourcesManager = ResourcesManager.getInstance();        // 是通过LoadedApk的getResources()方法来加载的        Resources resources = packageInfo.getResources(mainThread);        if (resources != null) {            if (displayId != Display.DEFAULT_DISPLAY                    || overrideConfiguration != null                    || (compatInfo != null && compatInfo.applicationScale                            != resources.getCompatibilityInfo().applicationScale)) {                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,                        overrideConfiguration, compatInfo);            }        }        mResources = resources;

继续追踪LoadedApk.java

    public Resources getResources(ActivityThread mainThread) {        // 我们看到采用了缓存策略,继续追踪        if (mResources == null) {            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);        }        return mResources;    }
    ActivityThread.java类    /**     * Creates the top level resources for the given package.     */    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,            String[] libDirs, int displayId, Configuration overrideConfiguration,            LoadedApk pkgInfo) {            // 通过资源管理类来加载,继续追踪        return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,                displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());    }
/**     * Creates the top level Resources for applications with the given compatibility info.     *     * @param resDir the resource directory.     * @param splitResDirs split resource directories.     * @param overlayDirs the resource overlay directories.     * @param libDirs the shared library resource dirs this app references.     * @param displayId display Id.     * @param overrideConfiguration override configurations.     * @param compatInfo the compatibility info. Must not be null.     */    Resources getTopLevelResources(String resDir, String[] splitResDirs,            String[] overlayDirs, String[] libDirs, int displayId,            Configuration overrideConfiguration, CompatibilityInfo compatInfo) {        ...省略不关心的代码...        Resources r;        ......        AssetManager assets = new AssetManager();        // resDir can be null if the 'android' package is creating a new Resources object.        // This is fine, since each AssetManager automatically loads the 'android' package        // already.        if (resDir != null) {            // 等下,我们还找着重看下,资源管理类是如何加载资源的            if (assets.addAssetPath(resDir) == 0) {                return null;            }        }        DisplayMetrics dm = getDisplayMetricsLocked(displayId);        Configuration config;        final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);        final boolean hasOverrideConfig = key.hasOverrideConfiguration();        if (!isDefaultDisplay || hasOverrideConfig) {            config = new Configuration(getConfiguration());            if (!isDefaultDisplay) {                applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);            }            if (hasOverrideConfig) {                config.updateFrom(key.mOverrideConfiguration);                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);            }        } else {            config = getConfiguration();        }        // 可以看到Resources类最终是通过new来进行实例化的        r = new Resources(assets, dm, config, compatInfo);
    这里附下AssetManager.java的添加资(产)源文件的方法,可以接收资产文件的目录,或者是压缩文件的路径,    另外观察到该方法是隐藏的方法,是调用不到的,只能通过反射   /**     * Add an additional set of assets to the asset manager.  This can be     * either a directory or ZIP file.  Not for use by applications.  Returns     * the cookie of the added asset, or 0 on failure.     * {@hide}     */    public final int addAssetPath(String path) {        synchronized (this) {            int res = addAssetPathNative(path);            makeStringBlocks(mStringBlocks);            return res;        }    }

4.总结下Resources实例的创建流程

packageInfo.getResources(mainThread) --> mainThread.getTopLevelResources --> mResourceManager.getTopLevelResources() --> r = new Resources(assets, dm, config, compatInfo);

好了,我们知道了Resources的创建流程,就可以自己来实例化一个Resources对象,然后加载一个我们本地的资源文件,我们可以用来做一键换肤功能;

5.通过Resources类,加载另外一个apk文件中的资源

我事先准备了一个plugin.apk,在其res/drawable目录下有一个skin2.png图片,下面我们实现一键切换实现图片应用到到我们apk中来,我们修改该apk后缀为plugin.zip,然后放置到SD卡的跟目下备用;新建一个工程,在布局文件中放置一个Button和ImageView,比较简单,上代码不解释.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <!-- 无论该根布局是线性还是相对,我们都可以添加NavigationBar了 -->    <Button        android:text="一键换肤"        android:id="@+id/btn_change_skin"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <ImageView        android:id="@+id/iv_skin"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:src="@drawable/skin1" /></LinearLayout>

在代码中为Button添加点击事件,我们实现一键实现切换图片的功能(一键换肤)

    final ImageView ivSkin = (ImageView) findViewById(R.id.iv_skin);       findViewById(R.id.btn_change_skin).setOnClickListener(new View.OnClickListener() {           @Override           public void onClick(View v) {               //AssetManager assets = new AssetManager();  // 使用{@hide} 标注,隐藏了,我们只能通过反射来调用了               AssetManager assets = null;               try {                   assets =AssetManager.class.newInstance();                   // 加载资源                   // assets.addAssetPath(String path) 又隐藏了 ,反射获取方法,然后再调方法                   Method method = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);                   // method.setAccessible(true);  如果是私用的,修改权限                   method.invoke(assets, Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "plugin.zip");                   Resources superRes = getResources();                   Resources resources = new Resources(assets,superRes.getDisplayMetrics(), superRes.getConfiguration());                    // 其实metrics和config都是可以直接new的,这里我们就不直接new了                    // DisplayMetrics mMetrics = new DisplayMetrics();                    // DisplayMetrics mMetrics = new DisplayMetrics();                   int drawableId = resources.getIdentifier("skin2","drawable","com.xialm.skinplugin");                   Drawable drawable = resources.getDrawable(drawableId);                   ivSkin.setImageDrawable(drawable);               } catch (Exception e) {                   e.printStackTrace();               }           }       });

附效果图为:
这里写图片描述

1 0