0xA04 Android 10 源碼分析:Apk加載流程之資源加載(二)

引言

  • 這是 Android 10 源碼分析系列的第 4 篇
  • 分支:android-10.0.0_r14
  • 全文閱讀大概 10 分鐘

經過這篇文章你將學習到如下內容,將在文末會給出相應的答案java

  • View中的INVISIBLE、VISIBLE、GONE都有什麼做用?
  • 爲何ViewStub是大小爲0的視圖
  • ViewStub有什麼做用?
  • ViewStub是如何建立的?
  • 爲何ViewStub能作到延遲加載?
  • ViewStub指定的Layout佈局文件是何時被加載的?
  • LayoutInflater是一個抽象類它如何被建立的?
  • 系統服務存儲在哪裏?如何獲取和添加系統服務?

在上一篇文章 0xA02 Android 10 源碼分析:Apk加載流程之資源加載 中經過LayoutInflater.inflate方法解析xml文件,瞭解到了系統如何對merge、include標籤是如何處理的,本文主要圍繞如下兩方面內容android

  • 系統對ViewStub如何處理?
  • LayoutInflater是如何被建立的?

系統對merge、include是如何處理的算法

  • 使用merge標籤必須有父佈局,且依賴於父佈局加載
  • merge並非一個ViewGroup,也不是一個View,它至關於聲明瞭一些視圖,等待被添加,解析過程當中遇到merge標籤會將merge標籤下面的全部子view添加到根佈局中
  • merge標籤在 XML 中必須是根元素
  • 相反的include不能做爲根元素,須要放在一個ViewGroup中
  • 使用 include 標籤必須指定有效的 layout 屬性
  • 使用 include 標籤不寫寬高是沒有關係的,會去解析被 include 的 layout

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,而後添加到根佈局中
更多信息查看0xA02 Android 10 源碼分析:Apk加載流程之資源加載,接下來看一下系統對ViewStub如何處理bash

1. ViewStub是什麼

關於ViewStub的介紹,能夠點擊下方官網連接查看 官網連接https://developer.android.google.cn/reference/android...ViewStub網絡

ViewStub的繼承結構 app

簡單來講主要如下幾點:ide

  • ViewStub控件是一個不可見,
  • 大小爲0的視圖
  • 當ViewStub控件設置可見,或者調用inflate()方法,ViewStub所指定的layout資源就會被加載
  • ViewStub也會從其父控件中移除,ViewStub會被新加載的layout文件代替

爲何ViewStub是大小爲0的視圖 frameworks/base/core/java/android/view/ViewStub.java函數

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 設置視圖大小爲0
    setMeasuredDimension(0, 0);
}
複製代碼

ViewStub的做用

主要用來延遲佈局的加載,例如:在Android中很是常見的佈局,用ListView來展現列表信息,當沒有數據或者網絡加載失敗時, 加載空的 ListView 會佔用一些資源,若是用ViewStub包裹ListView,當有數據時,纔會調用inflate()方法顯示ListView,起到延遲加載了佈局效果

1.1 ViewStub 是如何被建立的

在上篇文章0xA02 Android 10 源碼分析:Apk加載流程之資源加載中,介紹了view的建立是經過調用了LayoutInflater.createView方法根據完整的類的路徑名利用反射機制構建View對象,由於ViewStub是繼承View,因此ViewStub的建立和View的建立是相同的,來看一下LayoutInflater.createView方法 frameworks/base/core/java/android/view/LayoutInflater.java

...

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;
}

...
複製代碼

根據完整的類的路徑名利用反射機制構建View對象,若是遇到ViewStub將當前LayoutInflater設置給ViewStub,當ViewStub控件設置可見,或者調用inflate(),會調用LayoutInflater的inflate方法完成佈局加載,接下來分析ViewStub的構造方法

1.2 ViewStub的構造方法

在上面提到了根據完整的類的路徑名利用反射機制構建View對象,當view對象被建立的時候,會調用它的構造函數,來看一下ViewStub的構造方法

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context);

    final TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.ViewStub, defStyleAttr, defStyleRes);
    saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
            defStyleRes);
    // 解析xml中設置的 android:inflatedId 的屬性
    mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
    // 解析xml中設置的 android:layout 屬性
    mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
    // 解析xml中設置的 android:id 屬性
    mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
    a.recycle();
    // view不可見
    setVisibility(GONE);
    // 不會調用 onDraw 方法繪製內容
    setWillNotDraw(true);
}
複製代碼
  1. 獲取android:inflatedId、android:layout、android:id的值
  2. 調用setVisibility方法,設置View不可見
  3. 調用setWillNotDraw方法,不會調用 onDraw 方法繪製內容

在上面提到了若是想要加載ViewStub所指定的layout資源,須要設置ViewStub控件設置可見,或者調用inflate()方法,來看一下ViewStub的setVisibility方法

1.3 ViewStub的setVisibility方法

setVisibility(int visibility)方法,參數visibility對應三個值分別是INVISIBLE、VISIBLE、GONE

  • VISIBLE:視圖可見
  • INVISIBLE:視圖不可見的,它仍然佔用佈局的空間
  • GONE:視圖不可見,它不佔用佈局的空間

接下里查看一下ViewStub的setVisibility方法 frameworks/base/core/java/android/view/ViewStub.java

@Override
public void setVisibility(int visibility) {
    if (mInflatedViewRef != null) {
        // mInflatedViewRef 是 WeakReference的實例,調用inflate方法時候初始化
        View view = mInflatedViewRef.get();
        if (view != null) {
            // 設置View可見
            view.setVisibility(visibility);
        } else {
            throw new IllegalStateException("setVisibility called on un-referenced view");
        }
    } else {
        super.setVisibility(visibility);
        // 當View爲空且設置視圖可見(VISIBLE、INVISIBLE),調用inflate方法
        if (visibility == VISIBLE || visibility == INVISIBLE) {
            inflate();
        }
    }
}
複製代碼
  1. mInflatedViewRef 是 WeakReference的實例,調用inflate方法時候初始化
  2. 從 mInflatedViewRef 緩存中獲取View,而且設置View可見
  3. 當View爲空且設置視圖可見(VISIBLE、INVISIBLE),會調用inflate方法

1.4 ViewStub.inflate方法

調用了ViewStub的setVisibility方法,最後都會調用ViewStub.inflate方法,來查看一下 frameworks/base/core/java/android/view/ViewStub.java

public View inflate() {
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            // 解析佈局視圖
            // 返回的view是android:layout指定的佈局文件最頂層的view
            final View view = inflateViewNoAdd(parent);
            
            // 移除ViewStub
            // 添加view到被移除的ViewStub的位置
            replaceSelfWithView(view, parent);

            // 添加view到 mInflatedViewRef 中
            mInflatedViewRef = new WeakReference<>(view);
            if (mInflateListener != null) {
                // 加載完成以後,回調onInflate 方法
                mInflateListener.onInflate(this, view);
            }

            return view;
        } else {
            // 須要在xml中設置android:layout,不是layout,不然會拋出異常
            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
        }
    } else {
        // ViewStub不能做爲根佈局,它須要放在ViewGroup中, 不然會拋出異常
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
}
複製代碼
  1. 調用inflateViewNoAdd方法返回android:layout指定的佈局文件最頂層的view
  2. 調用replaceSelfWithView方法, 移除ViewStub, 添加view到被移除的ViewStub的位置
  3. 添加view到 mInflatedViewRef 中
  4. 加載完成以後,回調onInflate 方法

須要注意如下兩點:

  • 使用ViewStub須要在xml中設置android:layout,不是layout,不然會拋出異常
  • ViewStub不能做爲根佈局,它須要放在ViewGroup中, 不然會拋出異常

來查看一下inflateViewNoAdd方法和replaceSelfWithView方法

1.5 ViewStub.inflateViewNoAdd方法

調用inflateViewNoAdd方法返回android:layout指定的佈局文件最頂層的view frameworks/base/core/java/android/view/ViewStub.java

private View inflateViewNoAdd(ViewGroup parent) {
    final LayoutInflater factory;

    // mInflater 是View被建立的時候,若是是ViewStub, 將LayoutInflater賦值給mInflater
    if (mInflater != null) {
        factory = mInflater;
    } else {
        // 若是mInflater爲空,則建立LayoutInflater
        factory = LayoutInflater.from(mContext);
    }

    // 從指定的 mLayoutResource 資源中解析佈局視圖
    // mLayoutResource 是在xml設置的 Android:layout 指定的佈局文件
    final View view = factory.inflate(mLayoutResource, parent, false);

    // mInflatedId 是在xml設置的 inflateId
    if (mInflatedId != NO_ID) {
        // 將id複製給view
        view.setId(mInflatedId);
        //注意:若是指定了mInflatedId , 被inflate的layoutView的id就是mInflatedId
    }
    return view;
}
複製代碼
  • mInflater是View被建立的時候,若是是ViewStub, 將LayoutInflater賦值給mInflater
  • 若是mInflater爲空則經過LayoutInflater.from(mContext)構建LayoutInflater
  • 調用LayoutInflater的inflate方法解析佈局視圖
  • 將mInflatedId設置View

1.6 ViewStub.replaceSelfWithView方法

調用replaceSelfWithView方法, 移除ViewStub, 添加view到被移除的ViewStub的位置 frameworks/base/core/java/android/view/ViewStub.java

private void replaceSelfWithView(View view, ViewGroup parent) {
    // 獲取ViewStub在視圖中的位置
    final int index = parent.indexOfChild(this);
    // 移除ViewStub
    // 注意:調用removeViewInLayout方法以後,調用findViewById()是找不到該ViewStub對象
    parent.removeViewInLayout(this);

    final ViewGroup.LayoutParams layoutParams = getLayoutParams();
    // 將xml中指定的 android:layout 佈局文件中最頂層的View,添加到被移除的 ViewStub的位置
    if (layoutParams != null) {
        parent.addView(view, index, layoutParams);
    } else {
        parent.addView(view, index);
    }
}
複製代碼
  1. 獲取ViewStub在視圖中的位置,而後移除ViewStub
  2. 添加android:layout 佈局文件中最頂層的View到被移除的ViewStub的位置

1.7 ViewStub的注意事項

  • 使用ViewStub須要在xml中設置android:layout,不是layout,不然會拋出異常
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
複製代碼
  • ViewStub不能做爲根佈局,它須要放在ViewGroup中, 不然會拋出異常
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
複製代碼
  • 一旦調用setVisibility(View.VISIBLE)或者inflate()方法以後,該ViewStub將會從試圖中被移除(此時調用findViewById()是找不到該ViewStub對象).
// 獲取ViewStub在視圖中的位置
final int index = parent.indexOfChild(this);
// 移除ViewStub
// 注意:調用removeViewInLayout方法以後,調用findViewById()是找不到該ViewStub對象
parent.removeViewInLayout(this);
複製代碼
  • 若是指定了mInflatedId , 被inflate的layoutView的id就是mInflatedId
// mInflatedId 是在xml設置的 inflateId
if (mInflatedId != NO_ID) {
    // 將id複製給view
    view.setId(mInflatedId);
    //注意:若是指定了mInflatedId , 被inflate的layoutView的id就是mInflatedId
}
複製代碼
  • 被inflate的layoutView的layoutParams與ViewStub的layoutParams相同.
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 將xml中指定的 android:layout 佈局文件中最頂層的View 也就是根view,
// 添加到被移除的 ViewStub的位置
if (layoutParams != null) {
    parent.addView(view, index, layoutParams);
} else {
    parent.addView(view, index);
}
複製代碼

到這裏關於ViewStub的構建、佈局的加載以及注意事項分析完了,接下來分析一下 LayoutInflater是如何被建立的

2 關於LayoutInflater

0xA02 Android 10 源碼分析:Apk加載流程之資源加載文章中,介紹了Activity啓動的時候經過調用LayoutInflater的inflater的方法加載layout文件,那麼LayoutInflater是如何被建立的呢,先來看一段代碼,相信下面的代碼都不會很陌生

public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    return new AllVh(LayoutInflater
            .from(viewGroup.getContext())
            .inflate(R.layout.list_item, viewGroup, false));
}
複製代碼

LayoutInflater的inflate方法的三個參數都表明什麼意思?

  • resource:要解析的xml佈局文件Id
  • root:表示根佈局
  • attachToRoot:是否要添加到父佈局root中

resource其實很好理解就是資源Id,而root 和 attachToRoot 分別表明什麼意思:

  • 當attachToRoot == true且root != null時,新解析出來的View會被add到root中去,而後將root做爲結果返回
  • 當attachToRoot == false且root != null時,新解析的View會直接做爲結果返回,並且root會爲新解析的View生成LayoutParams並設置到該View中去
  • 當attachToRoot == false且root == null時,新解析的View會直接做爲結果返回

2.1 LayoutInflater是如何被建立的

LayoutInflater是一個抽象類,經過調用了from()的靜態函數,經由系統服務LAYOUT_INFLATER_SERVICE,最終建立了一個LayoutInflater的子類對象PhoneLayoutInflater,繼承結構以下:

LayoutInflater.from(ctx) 就是根據傳遞過來的Context對象,調用getSystemService()來獲取對應的系統服務, 來看一下這個方法 frameworks/base/core/java/android/view/LayoutInflater.java

public static LayoutInflater from(Context context) {
    // 獲取系統服務 LAYOUT_INFLATER_SERVICE ,並賦值給 LayoutInflater
    // Context 是一個抽象類,真正的實現類是ContextImpl
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}
複製代碼

而Context自己是一個抽象類,它真正的實例化對象是ContextImpl frameworks/base/core/java/android/app/ContextImpl.java

public Object getSystemService(String name) {
    // SystemServiceRegistry 是管理系統服務的
    // 調用getSystemService方法,經過服務名字查找對應的服務
    return SystemServiceRegistry.getSystemService(this, name);
}
複製代碼

2.2 SystemServiceRegistry

SystemServiceRegistry管理全部的系統服務,調用getSystemService方法,經過服務名字查找對應的服務 frameworks/base/core/java/android/app/SystemServiceRegistry.java

public static Object getSystemService(ContextImpl ctx, String name) {
    // SYSTEM_SERVICE_FETCHERS 是一個map集合
    // 從 map 集合中取出系統服務
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}
複製代碼
  • ServiceFetcher爲SystemServiceRegistry類的靜態內部接口,定義了getService方法
  • ServiceFetcher的實現類CachedServiceFetcher實現了getService方法
  • 全部的系統服務都存儲在一個map集合SYSTEM_SERVICE_FETCHERS當中,調用get方法來獲取對應的服務

若是有getSystemService方法來獲取服務,那麼相應的也會有添加服務的方法 frameworks/base/core/java/android/app/SystemServiceRegistry.java

private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
複製代碼

經過調用SYSTEM_SERVICE_NAMES的put方法,往map集合中添加數據,那麼registerService是何時調用的,在SystemServiceRegistry類中搜索registerService方法,知道了在類加載的時候經過靜態代碼塊中添加的,來看一下

static {
    // 初始化加載全部的系統服務

    ...
    // 省略了不少系統服務

    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            return new PhoneLayoutInflater(ctx.getOuterContext());
        }});

    ...
    // 省略了不少系統服務
}
複製代碼

最終是建立了一個PhoneLayoutInflater並返回的,到這裏LayoutInflater的建立流程就分析完了

總結

View中的INVISIBLE、VISIBLE、GONE都有什麼做用?

若是想隱藏或者顯示View,能夠經過調用setVisibility(int visibility)方法來實現,參數visibility對應三個值分別是INVISIBLE、VISIBLE、GONE

  • VISIBLE:視圖可見
  • INVISIBLE:視圖不可見的,它仍然佔用佈局的空間
  • GONE:視圖不可見,它不佔用佈局的空間

爲何ViewStub是大小爲0的視圖? frameworks/base/core/java/android/view/ViewStub.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 設置視圖大小爲0
    setMeasuredDimension(0, 0);
}
複製代碼

ViewStub有什麼做用?

ViewStub的做用主要用來延遲佈局的加載

ViewStub是如何建立的?

由於ViewStub是繼承View, 因此ViewStub的建立和View的建立是相同的,經過調用了LayoutInflater.createView方法根據完整的類的路徑名利用反射機制構建View對象 frameworks/base/core/java/android/view/LayoutInflater.java

...

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;
}

...
複製代碼

爲何ViewStub能作到延遲加載?

由於在解析layout文件過程當中遇到ViewStub,只是構建ViewStub的對象和初始化ViewStub的屬性,沒有真正開始解析view,因此能夠作到延遲初始化

ViewStub指定的Layout佈局文件是何時被加載的?

當ViewStub控件設置可見,或者調用inflate()方法,ViewStub所指定的layout資源就會被加載

LayoutInflater是一個抽象類它如何被建立的?

LayoutInflater是一個抽象類,經過調用了from()的靜態函數,經由系統服務LAYOUT_INFLATER_SERVICE,最終建立了一個LayoutInflater的子類對象PhoneLayoutInflater,繼承結構以下:

LayoutInflater.from(ctx) 就是根據傳遞過來的Context對象,調用getSystemService()來獲取對應的系統服務

系統服務存儲在哪裏?如何獲取和添加系統服務?

SystemServiceRegistry管理全部的系統服務,全部的系統服務都存儲在一個map集合SYSTEM_SERVICE_FETCHERS當中,調用getSystemService方法獲取系統服務,調用registerService方法添加系統服務

結語

致力於分享一系列的Android系統源碼、逆向分析、算法相關的文章,每篇文章都會反覆檢查以後纔會發佈,若是你同我同樣喜歡研究Android源碼,一塊兒來學習,期待與你一塊兒成長

系列文章

Android 10 源碼系列:

工具系列:

逆向系列:

相關文章
相關標籤/搜索