### 拋出
1.compat庫是如何將`TextView`替換為`AppCompatTextView`的?
2.為什么要進行替換?
3.根據替換相關原理,我們可以做哪些事情?
> Answer1:
###### 第二問:
先從第二問開始吧,`AppCompatTextView`繼承自`TextView`,是對`TextView`的一種擴展,因為在5.0中首次推出了`MaterialDesign`這種設計風格,但是眾所周知,5.0推出不可能所有的設備全部都更新到最新版本,為了在早期版本上實現新的功能(這些新功能比如從源碼注釋中解讀到`backgroundTint`屬性,根據文本內容自適應大小等),即為了新特性同樣可以兼容老版本,framework在創建`TextView`實例的時候,自動幫我們進行了替換。其它的AppCompatXXX與XXX的關系也是如此。
###### 第一問:
然后第一問,是如何完成替換的,我們這里只拿最直觀的流程舉例,且盡可能的簡化源碼過程,在討論這個問題之前,先了解幾個預備知識:
+ View是這么被解析創建出來的:
1.LayoutInflater:將布局XML文件實例化為其對應的View對象,我們在Activity中通過setCountentView傳入一個Layout的資源文件id,最終該方法調用到PhoneWindow的setContentView方法,這個方法里面有調用到`mLayoutInflater.inflate(layoutResID,mContentParent);`
2.inflate方法,該方法的作用是將指定的XML文件填充到View的層次結構中去,最終無論通過什么途徑調用到inflate方法,都會走到三個參數的重載方法這里:
```
return inflate(parser,root,attachToRoot);
```
parser你可以認為持有將Layout.XML解析后的數據,后兩個參數的意義如下:
+ root 為null,attachToRoot無意義,inflate返回的是當前XML對應的根布局。
+ root不為null且attachToRoot為true,則整個XML對應的布局就設置了根布局是root
+ root不為null且attachToRoot為false,則會將root的`LayoutParams`設置給當前的XML布局。
知道了LayoutInflate.inflate做了什么,再往下,inflate中會調用到`createViewFromTag`,從方法名就能知道,繼續往下走,我們離答案越來越近了。
`createViewFromTag`做的事情非常有意思:
```
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 && mPrivateFacotry !=null){
view =mPrivateFacotry.onCreateVoew(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 =createVoew(name,null,attrs);
}
}finally{
mConstructorArgs[0]=lastContext;
}
}
return view;
```
先看①這個if-else,條件是name中有沒有字符“.”,如果有我們會執行`onCreateView`,如果沒有會執行createView。name啥時候有點?自定義控件的時候。當是系統控件的時候,`createView`會有一個填充了第二個參數的調用:`createVoew(name,"android.view.",attrs);`補上了View控件的全路徑名,而自定義控件則不需要,因為傳入的name就是一個全路徑名。
為什么要全路徑名?因為View控件對象的創建是通過反射來實現的:
```
clazz =mContext.getClassLoader().loadClass(
prefix!= null ? (prefix+name):name).asSubclass(View.class);
...
constructor=clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name,constructor);
...
args[1]=attrs;
final View view =constructor.newInstance(args);
```
下面對這幾布做一個總結:
XML中保存了`ViewTree`的結構和View的相關標簽信息(包括View的類型和一些屬性值),然后這些信息會在后面通過反射的方式(如果沒有Factory2和Factory的話)創建實例對象,如果創建的是`ViewGroup`,則會對它的子View遍歷重復創建步驟,創建完View對象后,會add到對應的`ViewGroup`中,其中相關方法調用流程是:`inflate->rInflate->createViewFromTag->createView`
好像還是沒有看到資源替換?
我們只解釋了后半部分,沒有解釋前半部分,那么什么是Factory?
繼續往下看:
createViewFromTag中會先判斷有沒有Factory或者Factory2的對象,如果有,則調用Factory的onCreateView方法,這兩個類都是接口,其中Factory2是Factory的子接口,都只有唯一一個onCreateView方法,不同之處在於Factory2的`onCreateView`方法傳入了`parentView`
**該方法的作用就是你可以借助它來改造XML中已經存在了的Tag的值**。所有Factory2可以達到改造parentView的目的。
但是我們日常中根本就沒有任何地方接觸到了Factory2,那么它是不是就直接是null呢?到這里又是一番源碼調來調去,為了便於理解,只需要知道,這個Factory2,在最開始`AppCompatActivity`(為了兼容低版本,我們現在Activity默認都繼承自它)中的`onCreate`方法中就已經通過層層調用被設置好了。
既然現在Factory2不為空,那么就應該去走它的onCreateView方法了,這里又是層層調用,最終來到了AppCompatViewInflater的onCreateView方法:

如果創建的是非兼容控件(系統控件那么多,實現兼容的只是常用的一些控件),那么就會是143行,在146行中通過反射創建View對象。
啰里啰嗦了一大堆,還是沒回答第一個問題:`compat`庫是如何將TextView替換為`AppCompatTextView`的?
> 個人對這個的理解:在將XML文件解析成包含ViewTree信息之后,開始利用這些信息去創建每一個View節點,在創建View對象的時候,如果發現這個節點是屬於支持兼容的控件比如`TextView`,那么就會去調用到`new AppCompatTextView()`來創建一個兼容View對象,也就是在創建的時候,就已經實現了替換。
第三問:
根據替換原理,我們可以做哪些事情?
整個替換從上面的源碼中就可以看到,能夠被替換的關鍵是`Factory2`存在,那么我覺得,其實問題問的是Factory2可以用來做什么?
[答案在這里](https://blog.csdn.net/lmj623565791/article/details/51503977)
參考鏈接:
[每日一問 AppCompatTextView 與 TextView](https://www.wanandroid.com/wenda/show/8832)
