Android中资源加载源码分析

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 的

` @Override
public 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类
@Override
public 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();
               }
           }
       });

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值