- 這是 Android 10 源碼分析系列的第 3 篇
- 分支:android-10.0.0_r14
- 全文閱讀大概 15 分鐘
經過這篇文章你將學習到如下內容,文末會給出相應的答案java
前面兩篇文章0xA01 Android 10 源碼分析:Apk是如何生成的 和 0xA02 Android 10 源碼分析:Apk的安裝流程分析了Apk大概能夠分爲代碼和資源兩部分,那麼Apk的加載也是分爲代碼和資源兩部分,代碼的加載涉及了進程的建立、啓動、調度,本文主要來分析一下資源的加載,若是沒有看過 Apk是如何生成的 和 Apk的安裝流程 能夠點擊下方鏈接前往:android
Android資源大概分爲兩個部分:assets 和 resgit
assets資源github
assets資源放在assets目錄下,它裏面保存一些原始的文件,能夠以任何方式來進行組織,這些文件最終會原封不動的被打包進APK文件中,經過AssetManager來獲取asset資源,代碼以下web
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是如何生成的
canvas
下面代碼必定不會很陌生,在Activity常見的幾行代碼緩存
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
}
複製代碼
一塊兒來分析一下調用setContentView方法以後作了什麼事情,接下來查看一下Activity中的setContentView方法 frameworks/base/core/java/android/app/Activity.javabash
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 源碼系列:
工具系列:
逆向系列: