Android中资源管理机制详细分析 - 新闻资讯 - 云南小程序开发|云南软件开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 技术分享 >

Android中资源管理机制详细分析

发表时间:2020-10-19

发布人:葵宇科技

浏览次数:50


尊敬原创:http://blog.csdn.net/yuanzeyao/article/details/42386549
在Android中,所有的资本都在res目次下存放,包含drawable,layout,strings,anim等等,当我们向工程中参加任何一个资本时,会在R类中响应会为该 资本分派一个id,我们在应用中就是经由过程这个id来拜访资本的,信赖做过Andorid开辟的同伙对于这些肯定不会陌生,所以这个也不是我今天想要说的,我今天想和大年夜家一路进修的是Android是若何治理资本的,在Android体系中,资本大年夜部分都是经由过程xml文件定义的(drawable是图片),如layout,string,anim都是xml文件,而对于layout,anim和strings等xml文件仅仅是解析xml文件,攫取指定的值罢了,然则对于layout文件中控件的解析就比较复杂了,例如对于一个Button,须要解析它所有的属性值,这个是若何实现的呢。
这里我们起重要推敲一个问题,就是一个控件有哪些属性是若何定义的?比如TextView具有哪些属性?为什愦我设置TextView的样式只能用style而不克不及用android:theme?这些信息都是在哪里定义的,想要弄清跋扈这个问题,就必须大年夜源码工程招谜底,我应用的是android4.1工程,如不雅你应用的是其他版本的,那么可能用些进出。
先看三个文件
1、d:\android4.1\frameworks\base\core\res\res\values\attrs.xml
看到attrs.xml文件,不知道你有没有想起什么?当我们在自定义控件的时刻,是不是会创建一个attrs.xml文件?应用attrs.xml文件的目标其实就是给我们自定义的控件添加属性,打开这个目次后,你会看到定义了一个叫"Theme"的styleable,如下(我只朝长进步部分)
<declare-styleable name="Theme">
        <!-- ============== -->
        <!-- Generic styles -->
        <!-- ============== -->
        <eat-comment />

        <!-- Default color of foreground imagery. -->
        <attr name="colorForeground" format="color" />
        <!-- Default color of foreground imagery on an inverted background. -->
        <attr name="colorForegroundInverse" format="color" />
        <!-- Color that matches (as closely as possible) the window background. -->
        <attr name="colorBackground" format="color" />

在这个文件中,定义了Android中大年夜部分可以应用的属性,这里我说的是“定义”而不是“声明”,同名在语法膳绫擎最大年夜的差别就是定义要有format属性,而声明没有format属性。
2、d:\android4.1\frameworks\base\core\res\res\values\attrs_manifest.xml
这个文件的名字和膳绫擎的文件的名字很像,就是多了一个manifest,故名思议就是定义了AndroidManifest.xml文件中的属性,这琅绫擎有一个很重要的一句话
<attr name="theme" format="reference" />

定义了一个theme属性,这个就是我们日常平凡在Activity膳绫擎应用的theme属性
3、d:\android4.1\frameworks\base\core\res\res\values\themes.xml
这个文件开端定义了一个叫做"Theme" 的sytle,如下(截图部分)
<style name="Theme">

        <item name="colorForeground">@android:color/bright_foreground_dark</item>
        <item name="colorForegroundInverse">@android:color/bright_foreground_dark_inverse</item>
        <item name="colorBackground">@android:color/background_dark</item>
        <item name="colorBackgroundCacheHint">?android:attr/colorBackground</item>

这个就是我们日常平凡在Application或者Activity中应用的Theme,大年夜这里可以看出,Theme也是一种style,那为什么style只能永远View/ViewGorup,而Theme只能用于Activity或者Application呢?先记住此问题,我们后续会为你解答
我们再来整合这三个文件的内容吧,起首在attrs.xml文件中,定义了Android中大年夜部分的属性,也就是说今后所有View/Activity中大年夜部分的属性就是在这里定义的,然后在attrs_manifest.xml中定义了一个叫做theme的属性,它的值就是再themes文件中定义的Theme或者持续自“Theme”的style。
有了膳绫擎的常识后,我们再来分析膳绫擎说过的两个问题:
1、TextView控件(其他控件也一样)的属性在哪里定义的。
2、既然Theme也是style,那为什么View只能用style,Activity只能应用theme?
所有View的属性定义都是在attrs.xml文件中的,所以我们到attrs.xml文件中寻找TextView的styleable吧
 <declare-styleable name="TextView">
        <!-- Determines the minimum type that getText() will return.
             The default is "normal".
             Note that EditText and LogTextBox always return Editable,
             even if you specify something less powerful here. -->
        <attr name="bufferType">
            <!-- Can return any CharSequence, possibly a
             Spanned one if the source text was Spanned. -->
            <enum name="normal" value=http://www.sjsjw.com/100/000252MYM024469/"0" />
            
            
            
            
        
        
        
        
        
        
        

膳绫擎的属性我只朝长进步了部分,请留意,这里所有的属性都是进行“声明”,你去搜刮这个styleable,会发明在TextView的styleable中不会找到theme这个属性的声明,所以你给任何一个view设置theme属性是没有效不雅的。请看下面一段代码就知道为什么了。
定义一个attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyTextView">
        <attr name="orientation">
            <enum name="horizontal" value=http://www.sjsjw.com/100/000252MYM024469/"0" />
        	
        
    
定义一个MyTextView
public class MyTextView extends TextView {
  private static final String TAG = "MyTextView";
  public MyTextView(Context context) 
  {
    super(context);
  }
  public MyTextView(Context context, AttributeSet attrs) 
  {
    super(context, attrs);
    //应用TypeArray攫取自定义的属性
    TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
    String value=http://www.sjsjw.com/100/000252MYM024469/ta.getString(R.styleable.MyTextView_orientation);
    Log.d("yzy", "value1--->"+value);
    ta.recycle();
  }
}

在attrs.xml我为MyTextView定义了一个orientation属性,然后再MyTextView的构造函数中去攫取这个属性,这里就涉及到TypeArray这个类,我们发明获得TypeArray须要传入R.style.MyTextView这个值,这个就是体系为我们拜访MyTextView这个styleable供给的一个id,当我们须要拿到orientation这个属性的值瓯,我们经由过程R.style.MyTextView_orientation拿到,因为MyTextView中没有定义或者声明theme属性,所以我们找不到R.styleable.MyTextView_theme这个id,所以导致我们无法解析它的theme属性。同样回到TextView这个styleable来,因为TextView的styleable中没有定义theme属性,所以theme对于TextView是没有效的。所以即使你在TextView琅绫擎参加theme属性,即使编译器不会给你报错,这个theme也是被忽视了的。
我们再来看看Activity的属性是若何定义的,因为Activity是在AndroidManigest.xml文件中定义的,所以我们到attrs_manifest.xml中查找。
    <declare-styleable name="AndroidManifestActivity" parent="AndroidManifestApplication">
        <!-- Required name of the class implementing the activity, deriving from
            {@link android.app.Activity}.  This is a fully
            qualified class name (for example, com.mycompany.myapp.MyActivity); as a
            short-hand if the first character of the class
            is a period then it is appended to your package name. -->
        <attr name="name" />
        <attr name="theme" />
        <attr name="label" />
        <attr name="description" />
        <attr name="icon" />
        <attr name="logo" />
        <attr name="launchMode" />
        <attr name="screenOrientation" />
        <attr name="configChanges" />
        <attr name="permission" />
        <attr name="multiprocess" />
        <attr name="process" />
        <attr name="taskAffinity" />
        <attr name="allowTaskReparenting" />
        <attr name="finishOnTaskLaunch" />
        <attr name="finishOnCloseSystemDialogs" />
        <attr name="clearTaskOnLaunch" />
        <attr name="noHistory" />
        <attr name="alwaysRetainTaskState" />
        <attr name="stateNotNeeded" />
        <attr name="excludeFromRecents" />
        <!-- Specify whether the activity is enabled or not (that is, can be instantiated by the system).
             It can also be specified for an application as a whole, in which case a value of "false"
             will override any component specific values (a value of "true" will not override the
             component specific values). -->
        <attr name="enabled" />
        <attr name="exported" />
        <!-- Specify the default soft-input mode for the main window of
             this activity.  A value besides "unspecified" here overrides
             any value in the theme. -->
        <attr name="windowSoftInputMode" />
        <attr name="immersive" />
        <attr name="hardwareAccelerated" />
        <attr name="uiOptions" />
        <attr name="parentActivityName" />
    </declare-styleable>

很明显,Activity对于的styleable中是声清楚明了theme的,所以它可以解析theme属性。
膳绫擎两个问题都已经解答完了,下面来评论辩论另一个话题,就是Resources的获取过程。
在我的别的一篇文┞仿曾经评论辩论过这个话题更深层次懂得Context 这里我们再来进修一下Resources的获取过程。
在Android体系中,获取Resources重要有两种办法,经由过程Context获取和PackageManager获取
起首,我们看看我们经由过程Context获取,下面这张图是Context相干类的类图
[img]http://img.blog.csdn.net/20150104103842705?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVhbnpleWFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
大年夜图中可以看出,Context有两个子类,一个是ContextWrapper,另一个是ContextImpl,而ContextWrapper依附于ContextImpl。结合源码,我们会发明,Context是一个抽象类,它的┞锋正实现类就是ContextImpl,而ContextWrapper就像他的名字一样,仅仅是对Context的一层包装,它的功能都是经由过程调用属性mBase完成,该mBase本质就是指向一个ContextImpl类型的变量。我们获取Resources时就是调用Context的getResources办法,那么我们直接看看ContextImpl的getResources办法吧
 @Override
    public Resources getResources() {
        return mResources;
    }

我们发明这个办法很简单,就是返回mResources属性,那么这个属性是在哪里 赋值的呢,经由过程寻找发明,其实就是在创建ContextImpl,经由过程调用Init进行赋值的(具体逻辑参照《更深层次懂得Context》).这里我先给出getResource办法的时序图,然后跟踪源码。
[img]http://img.blog.csdn.net/20150104110812840?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVhbnpleWFv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
先大年夜init办法开端吧
final void init(LoadedApk packageInfo,
                IBinder activityToken, ActivityThread mainThread,
                Resources container, String basePackageName) {
        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(), container.getCompatibilityInfo());
        }
        mMainThread = mainThread;
        mContentResolver = new ApplicationContentResolver(this, mainThread);

        setActivityToken(activityToken);
    }

我们发明,对mResource进行赋值,是经由过程调用LoadedApk中的getResource进行的,传入了ActivityThead类型的参数
  public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir, this);
        }
        return mResources;
    }

在getResources办法中,其实就是调用了ActivityThrad的getTopLevelResources办法,个中mResDir就是apk文件的路径(对于用户安装的app,此路径就在/data/app下面的某一个apk),大年夜时序图中可以知道,getTopLevelResources其实就是调用了一个同名办法,我们直接看它的同名办法吧
Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
        ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
        Resources r;
        synchronized (mPackages) {
            // Resources is app scale dependent.
            if (false) {
                Slog.w(TAG, "getTopLevelResources: " + resDir + " / "
                        + compInfo.applicationScale);
            }
            WeakReference<Resources> 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;
            }
        };


        //if (r != null) {
        //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
        //            + r + " " + resDir);
        //}

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

        //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
        DisplayMetrics metrics = getDisplayMetricsLocked(null, false);
        r = new Resources(assets, metrics, getConfiguration(), compInfo);
        if (false) {
            Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
                    + r.getConfiguration() + " appScale="
                    + r.getCompatibilityInfo().applicationScale);
        }
        
        synchronized (mPackages) {
            WeakReference<Resources> wr = mActiveResources.get(key);
            Resources existing = wr != null ? wr.get() : null;
            if (existing != null && existing.getAssets().isUpToDate()) {
                // Someone else already created the resources while we were
                // unlocked; go ahead and use theirs.
                r.getAssets().close();
                return existing;
            }
            
            // XXX need to remove entries when weak references go away
            mActiveResources.put(key, new WeakReference<Resources>(r));
            return r;
        }
    }

这段代码的逻辑不复杂,起首大年夜mActiveResouuces中经由过程key拿到资本,如不雅资本不为null,并且是最新的,那么直接返回,不然创建一个AssetManager对象,并调用AssetManager的addAssetPath办法,然后应用创建的AssetManager为参数,创建一个Resources对象,保存并返回。经由过程膳绫擎的时序图,我们发明在创建AssetManager的时刻,在其构造函数中调用init办法,我们看看init办法做了什么吧
private native final void init();

居然是一个本处所法,那么我们只有看看对应的Jni代码了
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{
    AssetManager* am = new AssetManager();
    if (am == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", "");
        return;
    }

    am->addDefaultAssets();

    ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
    env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}

这个琅绫擎调用了本地的AssetManager的addDefaultAssets办法
bool AssetManager::addDefaultAssets()
{
    const char* root = getenv("ANDROID_ROOT");
    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");

    String8 path(root);
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}

这例的ANDROID_ROOT保存的就是/system路径,而kSystemAssets是 
static const char* kSystemAssets = "framework/framework-res.apk";

还记得framework-res.apk是什么吗,就是体系所有的资本文件。
到这里终于明白了,道理就是将体系的资本加载进来。
接下来看看addAssetPath办法吧,进入源码后,你会发明它也是一个本处所法,也须要看jni代码
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
                                                       jstring path)
{
    ScopedUtfChars path8(env, path);
    if (path8.c_str() == NULL) {
        return 0;
    }

    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }

    void* cookie;
    bool res = am->addAssetPath(String8(path8.c_str()), &cookie);

    return (res) ? (jint)cookie : 0;
}

这里调用了本地AssetManager办法的addAssetPath办法。和体系资本一样,都被加载进来了。
下面看看PackageManager获取Resource的流程吧
在PackageManager琅绫擎获取资本调用的是getResourcesForApplication办法,getResourcesForApplication也有一个同名办法,我们看办正事的那个吧,
    @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, mContext.mPackageInfo);
        if (r != null) {
            return r;
        }
        throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
    }
起首断定包名是否是system,如不雅不是那么直接调用ActivityThread的getTopLevelResources办法。不过这里会根据当前应用的应用的uid和过程Id相等,如不雅相等则传入app.sourceDir,不然传入publicSourceDir,然则根据经验时代sourceDir和publicSource一般情况下是雷同的。后面的逻辑和Context中的是一样的,这里就不在说了。

相关案例查看更多