欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Android中View創(chuàng)建的全過程

 更新時(shí)間:2017年03月02日 11:59:10   作者:jacpy  
這篇文章主要給大家深入的解析了關(guān)于Android中View創(chuàng)建的全過程,文中介紹的非常詳細(xì),相信對(duì)大家會(huì)有一定的參考借鑒,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。

前言

吸進(jìn)這幾天在看View的尺寸是怎樣計(jì)算出來的,于是看了整個(gè)View被初始化的過程,結(jié)合系統(tǒng)源碼總結(jié)了一下分享出來,方便需要的朋友或者自己以后有需要的時(shí)候看看,下面話不多說了,來看看詳細(xì)的介紹吧。

從布局文件到LayoutParams

首先從Activity的setContentView(int)方法開始,只要設(shè)置了R.layout的布局文件,那么界面上就會(huì)顯示出來對(duì)應(yīng)的內(nèi)容。所以以這個(gè)方法為初發(fā)點(diǎn),然后往后跟蹤代碼。

public void setContentView(@LayoutRes int layoutResID) {
 getWindow().setContentView(layoutResID);
 initWindowDecorActionBar();
}

通過以上代碼發(fā)現(xiàn)調(diào)用了Window類的setContentView方法,那么這個(gè)Window對(duì)象mWindow又是怎么初始化的?在Activity中搜索發(fā)現(xiàn)是在Activity的attach方法中初始化的,構(gòu)造了一個(gè)PhoneWindow對(duì)象。

如下代碼所示:

final void attach(Context context, ActivityThread aThread,
  Instrumentation instr, IBinder token, int ident,
  Application application, Intent intent, ActivityInfo info,
  CharSequence title, Activity parent, String id,
  NonConfigurationInstances lastNonConfigurationInstances,
  Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
 attachBaseContext(context);
 mFragments.attachHost(null /*parent*/);
 mWindow = new PhoneWindow(this); // 這里創(chuàng)建了Window對(duì)象
 mWindow.setCallback(this);
 mWindow.setOnWindowDismissedCallback(this);
 mWindow.getLayoutInflater().setPrivateFactory(this);
 // ... 中間部分代碼省略
 mWindow.setWindowManager(
   (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
   mToken, mComponent.flattenToString(),
   (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
 if (mParent != null) {
  mWindow.setContainer(mParent.getWindow());
 }
 mWindowManager = mWindow.getWindowManager();
 mCurrentConfig = config;
}

在PhoneWindow的setContentView(int)方法中,發(fā)現(xiàn)是調(diào)用了LayoutInflater的inflate(int, View)方法,對(duì)這個(gè)布局文件進(jìn)行轉(zhuǎn)換成View,并添加到后面View這個(gè)參數(shù)中。

@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)) {
  // ...
 } else {
  mLayoutInflater.inflate(layoutResID, mContentParent);
 }
 // ...
}

這里面順帶穿插說一下View的根節(jié)點(diǎn)是怎樣初始化出來的。

這里有一個(gè)關(guān)鍵的地方是這個(gè)installDecor()方法,在這個(gè)方法中通過調(diào)用generateDecor()方法創(chuàng)建了這個(gè)mDecor的對(duì)象,通過調(diào)用generateLayout(DecorView)方法初始化出來了mContentParent對(duì)象。其中,mDecor是PhoneWindow.DecorView的類實(shí)例,mContentParent是展示所有內(nèi)容的,是通過com.android.internal.R.id.contentID來找到這個(gè)View。

具體代碼如下所示:

protected ViewGroup generateLayout(DecorView decor) {
 // ...
 View in = mLayoutInflater.inflate(layoutResource, null); // layoutResource是根據(jù)對(duì)當(dāng)前顯示View的Activity的theme屬性值來決定由系統(tǒng)加載對(duì)應(yīng)的布局文件
 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 mContentRoot = (ViewGroup) in;
 
 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
 if (contentParent == null) {
 throw new RuntimeException("Window couldn't find content container view");
 }
 // ...
 return contentParent;
}

那么在哪里面可以看到這個(gè)DecorView呢?看下面圖。

下面這張圖是在debug模式下連接手機(jī)調(diào)試App,使用Layout Inspector工具查看得到的圖:


PhoneWindow.DecorView

其中從1位置可以看出,整個(gè)View的根節(jié)點(diǎn)的View是PhoneWindow.DecorView實(shí)例;從2位置和3位置的mId可以推斷出來,上面的mContentParent就是ContentFrameLayout類的實(shí)例;位置4中的藍(lán)色區(qū)域是mContentParent所表示的位置和大小。

以上圖是在AS 2.2.3版本上使用Android Monitor Tab頁中的Layout Inspector工具(參考位置5)生成。

緊接著跟蹤上面LayoutInflater中的inflate()方法中調(diào)用,發(fā)現(xiàn)最后調(diào)用到了
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)方法中,在這個(gè)方法中構(gòu)造了XmlResourceParser對(duì)象,而這個(gè)parser對(duì)象構(gòu)造代碼如下所示:

XmlResourceParser loadXmlResourceParser(int id, String type)
  throws NotFoundException {
 synchronized (mAccessLock) {
  TypedValue value = mTmpValue;
  if (value == null) {
   mTmpValue = value = new TypedValue();
  }
  getValue(id, value, true);
  if (value.type == TypedValue.TYPE_STRING) {
   return 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");
 }
}
XmlResourceParser loadXmlResourceParser(String file, int id,
  int assetCookie, String type) throws NotFoundException {
 if (id != 0) {
  // ...
  // These may be compiled...
  synchronized (mCachedXmlBlockIds) {
   // First see if this block is in our cache.
   final int num = mCachedXmlBlockIds.length;
   for (int i=0; i<num; i++) {
    if (mCachedXmlBlockIds[i] == id) {
     //System.out.println("**** REUSING XML BLOCK! id="
     //     + id + ", index=" + i);
     return mCachedXmlBlocks[i].newParser();
    }
   }
   // Not in the cache, create a new block and put it at
   // the next slot in the cache.
   XmlBlock block = mAssets.openXmlBlockAsset(
     assetCookie, file);
   if (block != null) {
    int pos = mLastCachedXmlBlockIndex+1;
    if (pos >= num) pos = 0;
    mLastCachedXmlBlockIndex = pos;
    XmlBlock oldBlock = mCachedXmlBlocks[pos];
    if (oldBlock != null) {
     oldBlock.close();
    }
    mCachedXmlBlockIds[pos] = id;
    mCachedXmlBlocks[pos] = block;
    //System.out.println("**** CACHING NEW XML BLOCK! id="
    //     + id + ", index=" + pos);
    return block.newParser();
   }
  }
  // ...
 }
 // ...
}

其中getValue()方法調(diào)用到本地frameworks/base/core/jni/android_util_AssetManager.cpp文件中的static jint android_content_AssetManager_loadResourceValue函數(shù),而在這個(gè)函數(shù)中java層的TypeValue的類對(duì)象value對(duì)象屬性通過JNI形式被賦值。

在構(gòu)建這個(gè)parser時(shí),經(jīng)歷了很復(fù)雜的過程,從TypeValue中的assetCookie屬性,到XmlBlock中的xmlBlock屬性,再到XmlBlock.Parser中的mParseState屬性,都是用來保存JNI層的數(shù)據(jù),因?yàn)樽罱K操作這些資源數(shù)據(jù)是在native層,所以必不可少通過JNI這種方式,在java層和native層周旋。這里沒有深入到native層去分析資源是如何被加載解析的,后面有機(jī)會(huì)再說。

最后跟到了這個(gè)public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法中,代碼如下所示:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
 synchronized (mConstructorArgs) {
 final Context inflaterContext = mContext;
 final AttributeSet attrs = Xml.asAttributeSet(parser);
 Context lastContext = (Context) mConstructorArgs[0];
 View result = root;
 // ...
 
 // Temp is the root view that was found in the xml
 final View temp = createViewFromTag(root, name, inflaterContext, attrs); // 這里將布局文件中的名稱反射成具體的View類對(duì)象
 ViewGroup.LayoutParams params = null;
 if (root != null) {
  // Create layout params that match root, if supplied
  params = root.generateLayoutParams(attrs); // 這里將尺寸轉(zhuǎn)換成了LayoutParams
  if (!attachToRoot) {
   // Set the layout params for temp if we are not
   // attaching. (If we are, we use addView, below)
   temp.setLayoutParams(params);
  }
 }
 // Inflate all children under temp against its context.
 rInflateChildren(parser, temp, attrs, true);
 // We are supposed to attach all the views we found (int temp)
 // to root. Do that now.
 if (root != null && attachToRoot) {
  root.addView(temp, params); // 將布局文件中根的View添加到mContentParent中
 }
  
 // ...
 return result;
}

接著看View的generateLayoutParams(AttributeSet)方法,因?yàn)檫@里返回了params。查看代碼最后發(fā)現(xiàn)LayoutParams的width和height屬性賦值的代碼如下所示:

public LayoutParams(Context c, AttributeSet attrs) {
 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
 setBaseAttributes(a,
   R.styleable.ViewGroup_Layout_layout_width,
   R.styleable.ViewGroup_Layout_layout_height);
 a.recycle();
}
 
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
 width = a.getLayoutDimension(widthAttr, "layout_width");
 height = a.getLayoutDimension(heightAttr, "layout_height");
}

通過查看TypedArray類中的getLayoutDimension()方法發(fā)現(xiàn),獲取的值是通過index在mData這個(gè)成員數(shù)組中獲取的。這個(gè)mData的值是在創(chuàng)建TypedArray對(duì)象時(shí)被賦的值,具體參見TypedArray的obtain方法。這個(gè)數(shù)組是在Resources的obtainStyledAttributes()方法中通過調(diào)用AssetManager.applyStyle()方法被初始化值的。applyStyle()方法是一個(gè)native方法,對(duì)應(yīng)frameworks/base/core/jni/android_util_AssetManager.cpp文件中的android_content_AssetManager_applyStyle函數(shù)。在這個(gè)函數(shù)中發(fā)現(xiàn),傳入的Resrouces類中的mTheme成員以及XmlBlock.Parse類的mParseState成員都是一個(gè)C++對(duì)象的指針,在java類中以整型或長(zhǎng)整型值保存。

至此,布局文件中的尺寸值已經(jīng)被轉(zhuǎn)換成了具體的int類型值。

從布局文件到View

從上面的public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法中看到View是通過這行代碼final View temp = createViewFromTag(root, name, inflaterContext, attrs);創(chuàng)建出來的,而這個(gè)name就是XML文件在解析時(shí)遇到的標(biāo)簽名稱,比如TextView。此時(shí)的attrs也就是上面分析的XmlBlock.Parser的對(duì)象。最后發(fā)現(xiàn)View是在createViewFromTag()方法中創(chuàng)建的,代碼如下所示:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
  boolean ignoreThemeAttr) {
 if (name.equals("view")) {
  name = attrs.getAttributeValue(null, "class");
 }
 
 // Apply a theme wrapper, if allowed and one is specified.
 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();
 }
 // ...
 View 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);
 }
 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;
  }
 }
 return view;
 
 // ...
}

這里要注意一下,mConstructorArgs的第一個(gè)值是一個(gè)Context,而這個(gè)Context有可能已經(jīng)不是當(dāng)前Activity的Context。

看到這里,下面這樣的代碼片段是不是很熟悉?

if (name.equals("view")) {
 name = attrs.getAttributeValue(null, "class");
}

上面Factory這個(gè)接口對(duì)象在LayoutInflater類中就有三個(gè)屬性,分別對(duì)應(yīng):factory、factory2、mPrivateFactory。很明顯,弄清了這三個(gè)對(duì)象,也就知道了View的初始化流程。下面代碼是對(duì)這三個(gè)屬性的值的輸出:

public class MainActivity extends AppCompatActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  LayoutInflater inflater = getLayoutInflater();
  LayoutInflater inflater1 = LayoutInflater.from(this);
  Field f = null;
  try {
   f = LayoutInflater.class.getDeclaredField("mPrivateFactory");
   f.setAccessible(true);
  } catch (NoSuchFieldException e) {
   e.printStackTrace();
  }
 Log.d("may", "the same object: " + (inflater == inflater1));
  Log.d("may", "inflater factory: " + inflater.getFactory() + ", factory2: " + inflater.getFactory2());
  Log.d("may", "inflater1 factory: " + inflater1.getFactory() + ", factory2: " + inflater1.getFactory2());
  if (f != null) {
   try {
    Log.d("may", "inflater mPrivateFactory: " + f.get(inflater));
    Log.d("may", "inflater1 mPrivateFactory: " + f.get(inflater1));
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   }
  }
 }
}

輸出的LOG如下所示:

// 當(dāng)前Activiy繼承的是android.support.v7.app.AppCompatActivity
the same object: true
inflater factory: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0}, factory2: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0}
inflater1 factory: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0}, factory2: android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC{android.support.v7.app.AppCompatDelegateImplV14@41fdf0b0}
inflater mPrivateFactory: com.jacpy.sb.MainActivity@41fd9e70
inflater1 mPrivateFactory: com.jacpy.sb.MainActivity@41fd9e70
// 當(dāng)前Activity繼承的是android.app.Activity
the same object: true
inflater factory: null, factory2: null
inflater1 factory: null, factory2: null
inflater mPrivateFactory: com.jacpy.sb.MainActivity@41fd9a28
inflater1 mPrivateFactory: com.jacpy.sb.MainActivity@41fd9a28

首先看到mPrivateFactory是當(dāng)前的Activity實(shí)例,因?yàn)锳ctivity也實(shí)現(xiàn)的Factory2接口。首先看LayoutInflater的創(chuàng)建過程,如下圖所示:

LayoutInflater初始化流程

而生成的PhoneLayoutInflater對(duì)象是緩存在ContextImpl類的屬性SYSTEM_SERVICE_MAP中,所以通過Context.LAYOUT_INFLATER_SERVIC去取,始終是同一個(gè)對(duì)象,當(dāng)然僅限于當(dāng)前Context中。

mPrivateFactory屬性的賦值是在Activity的attach()方法中,通過調(diào)用mWindow.getLayoutInflater().setPrivateFactory(this); ,因此調(diào)用Factory2的onCreateView()方法時(shí),實(shí)際是調(diào)用Activity中的onCreateView()方法。而Activity中的onCreateView()實(shí)際返回的是null,所以最后創(chuàng)建View的是if判斷中的onCreateView(parent, name, attrs)方法,最后View是在LayoutInflater類中的public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException方法中創(chuàng)建:

public final View createView(String name, String prefix, AttributeSet attrs)
  throws ClassNotFoundException, InflateException {
 Constructor<? extends View> constructor = sConstructorMap.get(name);
 Class<? extends View> clazz = 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); // prefix傳的值是android.view.
 
 // ...
 
 constructor = clazz.getConstructor(mConstructorSignature); // Class<?>[] mConstructorSignature = new Class[] {
   Context.class, AttributeSet.class};
 constructor.setAccessible(true);
 sConstructorMap.put(name, constructor);
 // ...
 Object[] args = mConstructorArgs;
 args[1] = attrs; // 這個(gè)值是XmlBlock.Parser對(duì)象
 final View view = constructor.newInstance(args);
 if (view instanceof ViewStub) {
  // Use the same context when inflating ViewStub later.
  final ViewStub viewStub = (ViewStub) view;
  viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
 }
 return view;
}

這里有沒有發(fā)現(xiàn)mConstructorSignature數(shù)組的長(zhǎng)度決定了調(diào)用了View的哪個(gè)構(gòu)造方法?

總結(jié)

好了,以上就是這篇文章的全部?jī)?nèi)容了,至此,View已經(jīng)創(chuàng)建成功。希望本文的內(nèi)容對(duì)各位Android開發(fā)者們能帶來一定的幫助,如果有疑問大家可以留言交流。

相關(guān)文章

  • Android開發(fā)之CheckBox的簡(jiǎn)單使用與監(jiān)聽功能示例

    Android開發(fā)之CheckBox的簡(jiǎn)單使用與監(jiān)聽功能示例

    這篇文章主要介紹了Android開發(fā)之CheckBox的簡(jiǎn)單使用與監(jiān)聽功能,結(jié)合簡(jiǎn)單實(shí)例形式分析了Android使用CheckBox控件的布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-07-07
  • Kotlin數(shù)據(jù)容器深入講解

    Kotlin數(shù)據(jù)容器深入講解

    Kotlin的數(shù)據(jù)容器分為數(shù)組和集合。其中集合分為集合Set、隊(duì)列List、映射Map等三種集合,每種又包括只讀和可變兩種類型,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-09-09
  • Android Studio創(chuàng)建AIDL文件并實(shí)現(xiàn)進(jìn)程間通訊實(shí)例

    Android Studio創(chuàng)建AIDL文件并實(shí)現(xiàn)進(jìn)程間通訊實(shí)例

    本篇文章主要介紹了Android Studio創(chuàng)建AIDL文件并實(shí)現(xiàn)進(jìn)程間通訊實(shí)例,具有一定的參考價(jià)值,有興趣可以了解一下。
    2017-04-04
  • android實(shí)現(xiàn)session保持簡(jiǎn)要概述及實(shí)現(xiàn)

    android實(shí)現(xiàn)session保持簡(jiǎn)要概述及實(shí)現(xiàn)

    其實(shí)sesion在瀏覽器和web服務(wù)器直接是通過一個(gè)叫做name為sessionid的cookie來傳遞的,所以只要在每次數(shù)據(jù)請(qǐng)求時(shí)保持sessionid是同一個(gè)不變就可以用到web的session了,感興趣的你可以參考下本文或許對(duì)你有所幫助
    2013-03-03
  • 深踩Android Studio 緩存的坑及解決方法

    深踩Android Studio 緩存的坑及解決方法

    這篇文章主要介紹了深踩Android Studio 緩存的坑及解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03
  • 實(shí)例講解Android自定義控件

    實(shí)例講解Android自定義控件

    本篇文章通過實(shí)例給大家講解了Android自定義控件的使用技巧和需要注意的地方,跟著學(xué)習(xí)參考下吧。
    2017-12-12
  • 使用RoundedBitmapDrawable生成圓角圖片的方法

    使用RoundedBitmapDrawable生成圓角圖片的方法

    由于RoundedBitmapDrawable類沒有直接提供生成圓形圖片的方法,所以生成圓形圖片首先需要對(duì)原始圖片進(jìn)行裁剪,將圖片裁剪成正方形,最后再生成圓形圖片,具體實(shí)現(xiàn)方法,可以參考下本文
    2016-09-09
  • CDC與BG-CDC的含義電容觸控學(xué)習(xí)整理

    CDC與BG-CDC的含義電容觸控學(xué)習(xí)整理

    今天小編就為大家分享一篇關(guān)于CDC與BG-CDC的含義電容觸控學(xué)習(xí)整理,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • Android ProgressDialog的實(shí)例詳解

    Android ProgressDialog的實(shí)例詳解

    這篇文章主要介紹了Android ProgressDialog的實(shí)例詳解的相關(guān)資料,Android 開發(fā)項(xiàng)目的時(shí)候經(jīng)常會(huì)遇到耗時(shí)的操作,這里就講下Android ProgressDialog的應(yīng)用,需要的朋友可以參考下
    2017-07-07
  • Android編程之交互對(duì)話框?qū)嵗郎\析

    Android編程之交互對(duì)話框?qū)嵗郎\析

    這篇文章主要介紹了Android編程之交互對(duì)話框,結(jié)合實(shí)例形式簡(jiǎn)單分析了Android交互對(duì)話框AlertDialog的功能、簡(jiǎn)單用法及相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2017-03-03

最新評(píng)論