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

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

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

前言

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

從布局文件到LayoutParams

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

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

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

如下代碼所示:

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對象
 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ā)現是調用了LayoutInflater的inflate(int, View)方法,對這個布局文件進行轉換成View,并添加到后面View這個參數中。

@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é)點是怎樣初始化出來的。

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

具體代碼如下所示:

protected ViewGroup generateLayout(DecorView decor) {
 // ...
 View in = mLayoutInflater.inflate(layoutResource, null); // layoutResource是根據對當前顯示View的Activity的theme屬性值來決定由系統(tǒ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;
}

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

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


PhoneWindow.DecorView

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

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

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

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()方法調用到本地frameworks/base/core/jni/android_util_AssetManager.cpp文件中的static jint android_content_AssetManager_loadResourceValue函數,而在這個函數中java層的TypeValue的類對象value對象屬性通過JNI形式被賦值。

在構建這個parser時,經歷了很復雜的過程,從TypeValue中的assetCookie屬性,到XmlBlock中的xmlBlock屬性,再到XmlBlock.Parser中的mParseState屬性,都是用來保存JNI層的數據,因為最終操作這些資源數據是在native層,所以必不可少通過JNI這種方式,在java層和native層周旋。這里沒有深入到native層去分析資源是如何被加載解析的,后面有機會再說。

最后跟到了這個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類對象
 ViewGroup.LayoutParams params = null;
 if (root != null) {
  // Create layout params that match root, if supplied
  params = root.generateLayoutParams(attrs); // 這里將尺寸轉換成了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)方法,因為這里返回了params。查看代碼最后發(fā)現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ā)現,獲取的值是通過index在mData這個成員數組中獲取的。這個mData的值是在創(chuàng)建TypedArray對象時被賦的值,具體參見TypedArray的obtain方法。這個數組是在Resources的obtainStyledAttributes()方法中通過調用AssetManager.applyStyle()方法被初始化值的。applyStyle()方法是一個native方法,對應frameworks/base/core/jni/android_util_AssetManager.cpp文件中的android_content_AssetManager_applyStyle函數。在這個函數中發(fā)現,傳入的Resrouces類中的mTheme成員以及XmlBlock.Parse類的mParseState成員都是一個C++對象的指針,在java類中以整型或長整型值保存。

至此,布局文件中的尺寸值已經被轉換成了具體的int類型值。

從布局文件到View

從上面的public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法中看到View是通過這行代碼final View temp = createViewFromTag(root, name, inflaterContext, attrs);創(chuàng)建出來的,而這個name就是XML文件在解析時遇到的標簽名稱,比如TextView。此時的attrs也就是上面分析的XmlBlock.Parser的對象。最后發(fā)現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的第一個值是一個Context,而這個Context有可能已經不是當前Activity的Context。

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

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

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

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如下所示:

// 當前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
// 當前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是當前的Activity實例,因為Activity也實現的Factory2接口。首先看LayoutInflater的創(chuàng)建過程,如下圖所示:

LayoutInflater初始化流程

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

mPrivateFactory屬性的賦值是在Activity的attach()方法中,通過調用mWindow.getLayoutInflater().setPrivateFactory(this); ,因此調用Factory2的onCreateView()方法時,實際是調用Activity中的onCreateView()方法。而Activity中的onCreateView()實際返回的是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; // 這個值是XmlBlock.Parser對象
 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ā)現mConstructorSignature數組的長度決定了調用了View的哪個構造方法?

總結

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

相關文章

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

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

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

    Kotlin數據容器深入講解

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

    Android Studio創(chuàng)建AIDL文件并實現進程間通訊實例

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

    android實現session保持簡要概述及實現

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

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

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

    實例講解Android自定義控件

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

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

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

    CDC與BG-CDC的含義電容觸控學習整理

    今天小編就為大家分享一篇關于CDC與BG-CDC的含義電容觸控學習整理,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • Android ProgressDialog的實例詳解

    Android ProgressDialog的實例詳解

    這篇文章主要介紹了Android ProgressDialog的實例詳解的相關資料,Android 開發(fā)項目的時候經常會遇到耗時的操作,這里就講下Android ProgressDialog的應用,需要的朋友可以參考下
    2017-07-07
  • Android編程之交互對話框實例淺析

    Android編程之交互對話框實例淺析

    這篇文章主要介紹了Android編程之交互對話框,結合實例形式簡單分析了Android交互對話框AlertDialog的功能、簡單用法及相關注意事項,需要的朋友可以參考下
    2017-03-03

最新評論