IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 布局原理与资源加载原理分析 -> 正文阅读

[移动开发]布局原理与资源加载原理分析

布局原理

我们要学习布局原理,就是要明白UI线程是怎么工作的,而在Android中,ActivityThread就是我们常说的主线程或UI线程,ActivityThread的main方法是整个APP的入口。因为我们学习的只是布局的原理,所以我们的代码只关注跟UI相关的代码。

ActivityThread

首先来看performLaunchActivity()方法,这是activity启动的核心方法。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ......
    Window window = null;
    if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
        window = r.mPendingRemoveWindow;
        r.mPendingRemoveWindow = null;
        r.mPendingRemoveWindowManager = null;
    }
    appContext.setOuterContext(activity);
    activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config,
            r.referrer, r.voiceInteractor, window, r.configCallback);
    ......
}

找到window的变量,这是Android视窗的顶层窗口,初始值为null,然后进入activity.attach()方法找到

mWindow = new PhoneWindow(this, window, activityConfigCallback);

在这里,对mWindow进行了实例化,通过查看Window类的注释可以知道,PhoneWindow是Window类的唯一抽象实现。Activity的setContentView()方法最终实现是调用PhoneWindow中的setContentView()方法的。

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

如果mContentParent为空,则会进入installDecor()方法。Decor是装饰的意思,在这个方法里,通过

mDecor = generateDecor(-1);

生成一个继承自FrameLayout的DecorView,DecorView是整个Window界面最顶层的View。接下来通过

mContentParent = generateLayout(mDecor);

生成布局,我们深入进去找到 // Inflate the window decor注释,这里就是解析顶级布局资源的地方,找到

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

在这里通过inflater.inflate()加载到root里去

final View root = inflater.inflate(layoutResource, null);

然后通过addView将布局添加到DecorView中去,接下来通过定位到了布局中的 com.android.internal.R.id.content,得到contentParent并返回。

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

我们回到PhoneWindow的setContentView方法,在完成installDecor()后,紧接着通过

mLayoutInflater.inflate(layoutResID, mContentParent)

将资源文件加载到mContentParent中,我们来关注这个inflate方法,inflate方法会一层层调用,最后调用到

inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot){
  ...
}

我们来看final View temp = createViewFromTag(root, name, inflaterContext, attrs)创建view的过程。

在初始化的时候,如果factory不为空,则view的创建会被拦截,调用factory.onCreateView()方法。否则view的创建会走下面代码段:

if (view == null) {
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;
    try {
        if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
        } else {
            view = createView(name, null, attrs);
        }
    } finally {
        mConstructorArgs[0] = lastContext;
    }
}

如果是xml的则会直接走createView(),如果是view的话,则会进入onCreateView(),最终也会进入createView()。深入该函数继续看,在这里会有一个sConstructorMap的变量,用于存放所有的构造方法,首先会检查是否存在name的构造方法缓存,如果不存在的话,通过反射获得其构造方法并存入sConstructorMap中。

 if (constructor == null) {
      // Class not found in the cache, see if it's real, and try to add it
      clazz = mContext.getClassLoader().loadClass(
              prefix != null ? (prefix + name) : name).asSubclass(View.class);

      if (mFilter != null && clazz != null) {
          boolean allowed = mFilter.onLoadClass(clazz);
          if (!allowed) {
              failNotAllowed(name, prefix, attrs);
          }
      }
      constructor = clazz.getConstructor(mConstructorSignature);
      constructor.setAccessible(true);
      sConstructorMap.put(name, constructor);
  }

然后调用constructor.newInstance()实例化view。

final View view = constructor.newInstance(args);
return view;

前面说们说了,如果工厂类不为空,则会拦截View的创建过程,那么现在我们来看一下这些工厂类到底是什么。

 public interface Factory {

    public View onCreateView(String name, Context context, AttributeSet attrs);
}

public interface Factory2 extends Factory {

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

两者都是要onCreateView函数,而Factory2比Factory多了一个父亲的参数。阅读到这里我们可以发现,如果我们要干预view的创建过程,一个是重写LayoutInflater方法,另一个可以实现自己的工厂类,拦截view的创建。

回到inflate()方法,在这里会涉及到一个attachToRoot的参数。如果该参数是false的话,就会在xml文件中获取到布局属性,如果传入为true的话,就需要在代码中手动去设置布局的属性。

params = root.generateLayoutParams(attrs);

if (!attachToRoot) {
      // Set the layout params for temp if we are not
      // attaching. (If we are, we use addView, below)
      temp.setLayoutParams(params);
  }

继续往下看,看到rInflateChildren(parser, temp, attrs, true),然后继续调用rInflate()函数。它会循环递归创建子布局,直到递归到达最深或者xml标签到END_TAG。

 void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

资源加载原理

我们将编译得到的apk包拖至AS中,可以看到apk中的文件,其中有一个resources.arsc二进制文件,它是应用包中的资源映射 。

还是回到ActivityThread.java,其中有一个handleBindApplication()的方法,注释有说
Register the UI Thread as a sensitive thread to the runtime,意思就是将ui线程注册为运行时的敏感线程。

首先,需要先创建一个Instrumentation仪表类实例,查看其提供的方法,比如

  • callActivityOnCreate
  • callApplicationOnCreate
  • newActivity
  • callActivityOnNewIntent

等基本上在application和activity的所有生命周期调用中,都会先调用instrumentation的相应方法。并且针对应用内的所有activity都生效。为程序员提供了一个强大的能力,有更多的可能性进入android app框架执行流程。

if (data.instrumentationName !=null) {
...
    java.lang.ClassLoader cl = instrContext.getClassLoader();
    mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
...
} else {
    mInstrumentation =newInstrumentation();
}

然后会调用data.info.makeApplication()方法创建一个Application对象,info是LoadedApk类的一个实例,包含了当前加载的apk的本地状态。

app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;

在方法中,先通过createAppContext()创建上下文,然后调用mInstrumentation的newApplication()方法得到application的实例对象。

ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
      cl, appClass, appContext);
appContext.setOuterContext(app);

然后创建ContextImpl实例,接下来从apk中获取资源并设置资源。

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
            null);
    context.setResources(packageInfo.getResources());
    return context;
}

在getResources()方法中,通过获取ResourcesManager的实例,并调用它的getResources()方法。

public Resources getResources() {
        if (mResources == null) {
            final String[] splitPaths;
            try {
                splitPaths = getSplitPaths(null);
            } catch (NameNotFoundException e) {
                // This should never fail.
                throw new AssertionError("null split not found");
            }

            mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                    splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                    getClassLoader());
        }
        return mResources;
    }

来看ResourcesManager的getResources()实现:

try {
    Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
    final ResourcesKey key = new ResourcesKey(
            resDir,
            splitResDirs,
            overlayDirs,
            libDirs,
            displayId,
            overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
            compatInfo);
    classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    return getOrCreateResources(activityToken, key, classLoader);
} finally {
    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}

新建一个ResourcesKey,然后将传入的classLoader一起,传给getOrcCreateResources()方法,传入的activityToken是从上一个方法传入的null。

获取资源实际是通过ResourcesImpl来完成,来看getOrcCreateResources()方法中,由于传入的activityToken为null,因此会进入else,首先通过findResourcesImplForKeyLocked()查找缓存,是否存在所需的ResourcesImpl。

private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        synchronized (this) {
            ...
            if (activityToken != null) {
                ...

            } else {
                ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);

                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
                }

            }

            ResourcesImpl resourcesImpl = createResourcesImpl(key);
            if (resourcesImpl == null) {
                return null;
            }

            mResourceImpls.put(key, new WeakReference<>(resourcesImpl));

            final Resources resources;
            if (activityToken != null) {
                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl, key.mCompatInfo);
            } else {
                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
            }
            return resources;
        }

第一次由于ResourcesImpl没有初始化,所以缓存不存在,所以会通过createResourcesImpl()去创建。

private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
    final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
    daj.setCompatibilityInfo(key.mCompatInfo);

    final AssetManager assets = createAssetManager(key);
    if (assets == null) {
        return null;
    }

    final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
    final Configuration config = generateConfig(key, dm);
    final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

    if (DEBUG) {
        Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
    }
    return impl;
}

在方法内,通过createAssetManager(key)创建一个AssetManager的实例去加载资源,而它的实现中又是通过调用assets.addAssetPath(key.mResDir)该方法内最后调用native方法完成资源加载。

当ResourcesImpl创建完成后,又会接着调用getOrCreateResourcesLocked()去初始化Resources对象实例。最终将包装好的resources作为资源类返回,资源的信息都被存储在Resources中的ResourcesImpl中的Asset对象中。

所以实际上,Resource和ResourceImpl都是包装的壳,最终资源的读取都是通过assets来进行的。

此外,还有这些重要的API需要我们了解:

/*package*/ native final int getResourceIdentifier(String name,String defType,String defPackage);
/*package*/ native final String getResourceName(int resid);
/*package*/ native final String getResourcePackageName(int resid);
/*package*/ native final String getResourceTypeName(int resid);
/*package*/ native final String getResourceEntryName(int resid);
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-15 23:49:37  更:2021-07-15 23:49:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 5:25:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码