- AndroidStudio:3.6.0
- 分支:android-10.0.0_r14
- 文中地址如果不能访问,需要科学上网
- 全文阅读大概15分钟
通过这篇文章你将学习到以下内容:
前面两篇文章0xA01 Android 10 源码分析:Apk是如何生成的 和 0xA02 Android 10 源码分析:Apk的安装流程分析了Apk大概可以分为代码和资源两部分,那么Apk的加载也是分为代码和资源两部分,代码的加载涉及了进程的创建、启动、调度,本文主要来分析一下资源的加载,如果没有看过 Apk是如何生成的 和 Apk的安装流程 可以点击下方连接前往:
Android资源大概分为两个部分:assets 和 res
assets资源
assets资源放在assets目录下,它里面保存一些原始的文件,可以以任何方式来进行组织,这些文件最终会原封不动的被打包进APK文件中,通过AssetManager来获取asset资源,代码如下
AssetManager assetManager = context.getAssets(); InputStream is = assetManager.open("fileName"); 复制代码
res资源
res资源放在主工程的res目录下,这类资源一般都会在编译阶段生成一个资源ID供我们使用,res目录包括animator、anim、 color、drawable、layout、menu、raw、values、xml等,通过getResource()去获取Resources对象
Resources res = getContext().getResources(); 复制代码
Apk的生成过程中,会生成资源索引表resources.arsc文件和R.java文件,前者资源索引表resources.arsc记录了所有的应用程序资源目录的信息,包括每一个资源名称、类型、值、ID以及所配置的维度信息,后者定义了各个资源ID常量,运行时通过Resources和 AssetManger共同完成资源的加载,如果资源是个文件,Resouces先根据资源id查找出文件名,AssetManger再根据文件名查找出具体的资源,关于resources.arsc,可以查看0xA01 ASOP应用框架:Apk是如何生成的
下面代码一定不会很陌生,在Activity常见的几行代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) } 复制代码
一起来分析一下调用setContentView方法之后做了什么事情,接下来查看一下Activity中的setContentView方法 frameworks/base/core/java/android/app/Activity.java
public void setContentView(@LayoutRes int layoutResID) { // 实际上调用的是PhoneWindow.setContentView方法 getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } 复制代码
调用getWindow方法返回的是mWindow,mWindow是Windowd对象,实际上是调用它的唯一实现类PhoneWindow.setContentView方法
PhoneWindow 是Window的唯一实现类,它的结构如下:
当调用Activity.setContentView方法实际上调用的是PhoneWindow.setContentView方法 frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public void setContentView(int layoutResID) { // mContentParent是id为ID_ANDROID_CONTENT的FrameLayout // 调用setContentView方法,就是给id为ID_ANDROID_CONTENT的view添加子view if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { // FEATURE_CONTENT_TRANSITIONS,则是标记当前内容加载有没有使用过度动画 // 如果内容已经加载过,并且不需要动画,则会调用removeAllViews mContentParent.removeAllViews(); } // 检查是否设置了FEATURE_CONTENT_TRANSITIONS if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { // 解析指定的xml资源文件 mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; } 复制代码
当调用PhoneWindow.setContentView方法,之后调用LayoutInflater.inflate方法,来解析xml资源文件 frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); } 复制代码
inflate它有多个重载方法,最后调用的是inflate(resource, root, root != null)方法 frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); // 根据xml预编译生成compiled_view.dex, 然后通过反射来生成对应的View,从而减少XmlPullParser解析Xml的时间 // 需要注意的是在目前的release版本中不支持使用 View view = tryInflatePrecompiled(resource, res, root, attachToRoot); if (view != null) { return view; } // 获取资源解析器 XmlResourceParser XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } 复制代码
这个方法主要做了三件事:
注意:在目前的release版本中不支持使用tryInflatePrecompiled方法源码如下:
private void initPrecompiledViews() { // Precompiled layouts are not supported in this release. // enabled 是否启动预编译布局,这里始终为false boolean enabled = false; initPrecompiledViews(enabled); } private void initPrecompiledViews(boolean enablePrecompiledViews) { mUseCompiledView = enablePrecompiledViews; if (!mUseCompiledView) { mPrecompiledClassLoader = null; return; } ... } View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, boolean attachToRoot) { // mUseCompiledView始终为false if (!mUseCompiledView) { return null; } // 获取需要解析的资源文件的 pkg 和 layout String pkg = res.getResourcePackageName(resource); String layout = res.getResourceEntryName(resource); try { // 根据mPrecompiledClassLoader通过反射获取预编译生成的view对象的Class类 Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader); Method inflater = clazz.getMethod(layout, Context.class, int.class); View view = (View) inflater.invoke(null, mContext, resource); if (view != null && root != null) { // 将生成的view 添加根布局中 XmlResourceParser parser = res.getLayout(resource); try { AttributeSet attrs = Xml.asAttributeSet(parser); advanceToRootNode(parser); ViewGroup.LayoutParams params = root.generateLayoutParams(attrs); // 如果 attachToRoot=true添加到根布局中 if (attachToRoot) { root.addView(view, params); } else { // 否者将获取到的根布局的LayoutParams,设置到生成的view中 view.setLayoutParams(params); } } finally { parser.close(); } } return view; } catch (Throwable e) { } finally { } return null; } 复制代码
了解了tryInflatePrecompiled方法之后,在来查看一下inflate方法中的三个参数都什么意思
resource其实很好理解就是资源Id,而root 和 attachToRoot 分别代表什么意思:
根据源码知道调用tryInflatePrecompiled方法返回的view为空,继续往下执行调用Resources的getLayout方法获取资源解析器 XmlResourceParser
上面说到XmlResourceParser是通过调用Resources的getLayout方法获取的,getLayout方法又去调用了Resources的loadXmlResourceParser方法 frameworks/base/core/java/android/content/res/Resources.java
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException { return loadXmlResourceParser(id, "layout"); } XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type) throws NotFoundException { // TypedValue 主要用来存储资源 final TypedValue value = obtainTempTypedValue(); try { final ResourcesImpl impl = mResourcesImpl; // 获取xml资源,保存到 TypedValue impl.getValue(id, value, true); if (value.type == TypedValue.TYPE_STRING) { // 为指定的xml资源,加载解析器 return impl.loadXmlResourceParser(value.string.toString(), id, value.assetCookie, type); } throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id) + " type #0x" + Integer.toHexString(value.type) + " is not valid"); } finally { releaseTempTypedValue(value); } } 复制代码
TypedValue是动态的数据容器,主要用来存储Resource的资源,获取xml资源保存到 TypedValue,之后调用 ResourcesImpl 的loadXmlResourceParser方法加载对应的解析器
ResourcesImpl实现了Resource的访问,它包含了AssetManager和所有的缓存,通过Resource的getValue方法获取xml资源保存到 TypedValue,之后就会调用ResourcesImpl的loadXmlResourceParser方法对该布局资源进行解析 frameworks/base/core/java/android/content/res/ResourcesImpl.java
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie, @NonNull String type) throws NotFoundException { if (id != 0) { try { synchronized (mCachedXmlBlocks) { final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies; final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles; final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; // 首先从缓存中查找xml资源 final int num = cachedXmlBlockFiles.length; for (int i = 0; i < num; i++) { if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null && cachedXmlBlockFiles[i].equals(file)) { // 调用newParser方法去构建一个XmlResourceParser对象,返回给调用者 return cachedXmlBlocks[i].newParser(id); } } // 如果缓存中没有,则创建XmlBlock,并将它放到缓存中 // XmlBlock是已编译的xml文件的一个包装类 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); if (block != null) { final int pos = (mLastCachedXmlBlockIndex + 1) % num; mLastCachedXmlBlockIndex = pos; final XmlBlock oldBlock = cachedXmlBlocks[pos]; if (oldBlock != null) { oldBlock.close(); } cachedXmlBlockCookies[pos] = assetCookie; cachedXmlBlockFiles[pos] = file; cachedXmlBlocks[pos] = block; // 调用newParser方法去构建一个XmlResourceParser对象,返回给调用者 return block.newParser(id); } } } catch (Exception e) { final NotFoundException rnf = new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id)); rnf.initCause(e); throw rnf; } } throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id)); } 复制代码
首先从缓存中查找xml资源之后调用newParser方法,如果缓存中没有,则调用AssetManger的openXmlBlockAsset方法创建一个XmlBlock,并将它放到缓存中,XmlBlock是已编译的xml文件的一个包装类 frameworks/base/core/java/android/content/res/AssetManager.java
XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException { Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); // 调用native方法nativeOpenXmlAsset, 加载指定的xml资源文件,得到ResXMLTree // xmlBlock是ResXMLTree对象的地址 final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); if (xmlBlock == 0) { throw new FileNotFoundException("Asset XML file: " + fileName); } // 创建XmlBlock,封装xmlBlock,返回给调用者 final XmlBlock block = new XmlBlock(this, xmlBlock); incRefsLocked(block.hashCode()); return block; } } 复制代码
最终调用native方法nativeOpenXmlAsset去打开指定的xml文件,加载对应的资源,来查看一下navtive方法NativeOpenXmlAsset frameworks/base/core/jni/android_util_AssetManager.cpp
// java方法对应的native方法 {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset} static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie, jstring asset_path) { ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); ... const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie); std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table); status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); asset.reset(); ... return reinterpret_cast<jlong>(xml_tree.release()); } 复制代码
当xmlBlock创建之后,会调用newParser方法,构建一个XmlResourceParser对象,返回给调用者
XmlBlock是已编译的xml文件的一个包装类,XmlResourceParser 负责对xml的标签进行遍历解析的,它的真正的实现是XmlBlock的内部类XmlBlock.Parser,而真正完成xml的遍历操作的函数都是由XmlBlock来实现的,为了提升效率都是通过JNI调用native的函数来做的,接下来查看一下newParser方法 frameworks/base/core/java/android/content/res/XmlBlock.java
public XmlResourceParser newParser(@AnyRes int resId) { synchronized (this) { // mNative是C++层的ResXMLTree对象的地址 if (mNative != 0) { // nativeCreateParseState方法根据 mNative 查找到ResXMLTree, // 在C++层构建一个ResXMLParser对象, // 构建Parser,封装ResXMLParser,返回给调用者 return new Parser(nativeCreateParseState(mNative, resId), this); } return null; } } 复制代码
这个方法做两件事
接下来查看一下native方法nativeCreateParseState frameworks/base/core/jni/android_util_XmlBlock.cpp
// java方法对应的native方法 { "nativeCreateParseState", "(JI)J", (void*) android_content_XmlBlock_nativeCreateParseState } static jlong android_content_XmlBlock_nativeCreateParseState(JNIEnv* env, jobject clazz, jlong token, jint res_id) { ResXMLTree* osb = reinterpret_cast<ResXMLTree*>(token); if (osb == NULL) { jniThrowNullPointerException(env, NULL); return 0; } ResXMLParser* st = new ResXMLParser(*osb); if (st == NULL) { jniThrowException(env, "java/lang/OutOfMemoryError", NULL); return 0; } st->setSourceResourceId(res_id); st->restart(); return reinterpret_cast<jlong>(st); } 复制代码
经过一系列的跳转,最后调用XmlBlock.newParser方法获取资源解析器 XmlResourceParser,之后回到LayoutInflater调用处inflate方法,然后调用rInflate方法解析View frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { // 获取context final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; // 存储根布局 View result = root; try { // 处理 START_TA G和 END_TAG advanceToRootNode(parser); final String name = parser.getName(); // 解析merge标签,rInflate方法会将merge标签下面的所有子view添加到根布局中 // 这也是为什么merge标签可以简化布局的效果 if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } // 解析merge标签下的所有的view,添加到根布局中 rInflate(parser, root, inflaterContext, attrs, false); } else { // 如果不是merge标签,调用createViewFromTag方法解析布局视图,这里的temp其实是我们xml里的top view final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; // 如果根布局不为空的话,且attachToRoot为false,为view设置布局参数 if (root != null) { // 获取根布局的LayoutParams params = root.generateLayoutParams(attrs); // attachToRoot为false,为view设置LayoutParams if (!attachToRoot) { temp.setLayoutParams(params); } } // 解析当前view下面的所有子view rInflateChildren(parser, temp, attrs, true); // 如果 root 不为空且 attachToRoot 为false,将解析出来的view 添加到根布局 if (root != null && attachToRoot) { root.addView(temp, params); } // 如果根布局为空 或者 attachToRoot 为false,返回当前的view if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { throw ie; } finally { } return result; } } 复制代码
通过分析源码知道了attachToRoot 和root的参数代表什么意思,这里总结一下:*
无论是不是merge标签,最后都会调用rInflate方法进行view树的解析,他们的区别在于,如果是merge标签传递的参数finishInflate是false,如果不是merge标签传递的参数finishInflate是true frameworks/base/core/java/android/view/LayoutInflater.java
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; // 逐个 view 解析 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)) { // 解析android:focusable="true", 获取view的焦点 pendingRequestFocus = true; consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { // 解析android:tag标签 parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { // 解析include标签,include标签不能作为根布局 if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { // merge标签必须作为根布局 throw new InflateException("<merge /> must be the root element"); } else { // 根据元素名解析,生成view final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // rInflateChildren方法内部调用的rInflate方法,深度优先遍历解析所有的子view rInflateChildren(parser, view, attrs, true); // 添加解析的view viewGroup.addView(view, params); } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } // 如果finishInflate为true,则调用onFinishInflate方法 if (finishInflate) { parent.onFinishInflate(); } } 复制代码
整个view树的解析过程如下:
注意:通过分析源码, 以下几点需要特别注意
在解析过程中调用createViewFromTag方法,根据元素名解析,生成对应的view,接下来查看一下createViewFromTag方法 frameworks/base/core/java/android/view/LayoutInflater.java
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { return createViewFromTag(parent, name, context, attrs, false); } View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // 如果设置了theme, 构建一个ContextThemeWrapper if (!ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } try { // 如果name是blink,则创建BlinkLayout // 如果设置factory,根据factory进行解析, 这是系统留给我们的Hook入口 View view = tryCreateView(parent, name, context, attrs); // 如果 tryCreateView方法返回的view为空,则判断是内置View还是自定义view // 如果是内置的View则调用onCreateView方法,如果是自定义view 则调用createView方法 if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { // 如果使用自定义View,需要在xml指定全路径的, // 例如:com.hi.dhl.CustomView,那么这里就有个.了 // 可以利用这一点判定是内置的View,还是自定义View if (-1 == name.indexOf('.')) { // 解析内置view view = onCreateView(context, parent, name, attrs); } else { // 解析自定义view view = createView(context, name, null, attrs); } /** * onCreateView方法与createView方法的区别 * onCreateView方法:会给内置的View前面加一个前缀,例如:android.widget,最终会调用createView方法 * createView方法: 据完整的类的路径名利用反射机制构建View对象 */ } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { throw ie; } catch (Exception e) { throw ie; } } 复制代码
在解析过程中,会先调用tryCreateView方法,来看一下tryCreateView方法内部做了什么 frameworks/base/core/java/android/view/LayoutInflater.java
public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { // BlinkLayout它是FrameLayout的子类,是LayoutInflater中的一个内部类, // 如果当前标签为TAG_1995,则创建一个隔500毫秒闪烁一次的BlinkLayout来承载它的布局内容 if (name.equals(TAG_1995)) { // Let's party like it's 1995! // 源码注释也很有意思,写了Let's party like it's 1995!, 据说是为了庆祝1995年的复活节 return new BlinkLayout(context, attrs); } // 如果设置factory,根据factory进行解析, 这是系统留给我们的Hook入口,我们可以人为的干涉系统创建View,添加更多的功能 if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } return view; } 复制代码
根据刚才的分析,会先调用tryCreateView方法,如果这个方法返回的view为空,然后会调用onCreateView方法对内置View进行解析,createView方法对自定义View进行解析
onCreateView方法与createView方法的有什么区别
来看一下这两个方法的实现,LayoutInflater是一个抽象类,我们实际使用的是 PhoneLayoutInflater,它的结构如下
PhoneLayoutInflater重写了LayoutInflater的onCreatView方法,这个方法就是给内置的View前面加一个前缀 frameworks/base/core/java/com/android/internal/policy/PhoneLayoutInflater.java
private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app." }; protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view = createView(name, prefix, attrs); if (view != null) { return view; } } catch (ClassNotFoundException e) { } } return super.onCreateView(name, attrs); } 复制代码
onCreateView方法会给内置的View前面加一个前缀,之后调用createView方法,真正的View构建还是在LayoutInflater的createView方法里完成的,createView方法根据完整的类的路径名利用反射机制构建View对象 frameworks/base/core/java/android/view/LayoutInflater.java
public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException { ... try { if (constructor == null) { // 如果在缓存中没有找到构造函数,则根据完整的类的路径名利用反射机制构建View对象 clazz = Class.forName(prefix != null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, viewContext, attrs); } } // 利用反射机制构建clazz, 将它的构造函数存入sConstructorMap中,下次可以直接从缓存中查找 constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { // 如果从缓存中找到了缓存的构造函数 if (mFilter != null) { Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // 根据完整的类的路径名利用反射机制构建View对象 clazz = Class.forName(prefix != null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class); ... } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, viewContext, attrs); } } } ... try { // 利用构造函数,创建View final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // 如果是ViewStub,则设置LayoutInflater final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view; } finally { mConstructorArgs[0] = lastContext; } } catch (NoSuchMethodException e) { throw ie; } catch (ClassCastException e) { throw ie; } catch (ClassNotFoundException e) { throw e; } catch (Exception e) { throw ie; } finally { } } 复制代码
到了这里关于Apk的布局xml资源文件的查找和解析 -> View的生成流程到这里就结束了
那我们就来依次来回答上面提出的几个问题
LayoutInflater的inflate的三个参数都代表什么意思?
resource其实很好理解就是资源Id,而root 和 attachToRoot 分别代表什么意思:
系统对merge、include是如何处理的
merge标签为什么可以起到优化布局的效果?
解析过程中遇到merge标签,会调用rInflate方法,部分代码如下
// 根据元素名解析,生成对应的view final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // rInflateChildren方法内部调用的rInflate方法,深度优先遍历解析所有的子view rInflateChildren(parser, view, attrs, true); // 添加解析的view viewGroup.addView(view, params); 复制代码
解析merge标签下面的所有子view,然后添加到根布局中
view是如何被实例化的?
view分为系统view和自定义view, 通过调用onCreateView与createView方法进行不同的处理
为什么复杂布局会产生卡顿?
BlinkLayout是什么?
BlinkLayout继承FrameLayout,是一种会闪烁的布局,被包裹的内容会一直闪烁,根据源码注释Let's party like it's 1995!,BlinkLayout是为了庆祝1995年的复活节, 有兴趣可以看看 reddit 上的讨论,来查看一下它的源码是如何实现的
private static class BlinkLayout extends FrameLayout { private static final int MESSAGE_BLINK = 0x42; private static final int BLINK_DELAY = 500; private boolean mBlink; private boolean mBlinkState; private final Handler mHandler; public BlinkLayout(Context context, AttributeSet attrs) { super(context, attrs); mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == MESSAGE_BLINK) { if (mBlink) { mBlinkState = !mBlinkState; // 每隔500ms循环调用 makeBlink(); } // 触发dispatchDraw invalidate(); return true; } return false; } }); } private void makeBlink() { // 发送延迟消息 Message message = mHandler.obtainMessage(MESSAGE_BLINK); mHandler.sendMessageDelayed(message, BLINK_DELAY); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mBlink = true; mBlinkState = true; makeBlink(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mBlink = false; mBlinkState = true; // 移除消息,避免内存泄露 mHandler.removeMessages(MESSAGE_BLINK); } @Override protected void dispatchDraw(Canvas canvas) { if (mBlinkState) { super.dispatchDraw(canvas); } } } 复制代码
通过源码分析可以看出,BlinkLayout 通过 Handler 每隔500ms发送消息,在 handleMessage 中循环调用 invalidate 方法,通过调用 invalidate 方法,来触发 dispatchDraw 方法,做到一闪一闪的效果
致力于分享一系列的Android系统源码、逆向分析、算法相关的文章,每篇文章都会反复检查之后才会发布,如果你同我一样喜欢研究Android源码,一起来学习,期待与你一起成长
Android 10 源码系列:
工具系列:
逆向系列: