<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android | 帶你探究 LayoutInflater 布局解析原理

          共 8537字,需瀏覽 18分鐘

           ·

          2020-11-05 22:57

          作者:彭旭銳

          鏈接:

          https://juejin.im/post/6886052422260228103

          本文由作者授權發(fā)布。

          前言

          • 在 Android UI 開發(fā)中,經常需要用到 LayoutInflater 類,它的基本作用是將 xml 布局文件解析成 View / View 樹。除了基本的布局解析功能,LayoutInflater 還可以用于實現(xiàn) 動態(tài)換膚、視圖轉換、屬性轉換 等需求。
          • 在這篇文章里,我將帶你理解 LayoutInflater 的源碼。另外,文末的應試建議也不要錯過哦,如果能幫上忙,請務必點贊加關注,這真的對我非常重要。

          目錄


          1. 獲取 LayoutInflater 對象

          @SystemService(Context.LAYOUT_INFLATER_SERVICE)
          public?abstract?class?LayoutInflater?{
          ????...
          }

          首先,你要獲得 LayoutInflater ?的實例,由于 LayoutInflater 是抽象類,不能直接創(chuàng)建對象,因此這里總結一下獲取 LayoutInflater 對象的方法。具體如下:

          • 1. View.inflate(...)
          public?static?View?inflate(Context?context,?@LayoutRes?int?resource,?ViewGroup?root)?{
          ????LayoutInflater?factory?=?LayoutInflater.from(context);
          ????return?factory.inflate(resource,?root);
          }
          • 2. Activity#getLayoutInflater()
          public?LayoutInflater?getLayoutInflater()?{
          ????return?getWindow().getLayoutInflater();
          }
          • 3. PhoneWindow#getLayoutInflater()
          private?LayoutInflater?mLayoutInflater;

          public?PhoneWindow(Context?context)?{
          ????super(context);
          ????mLayoutInflater?=?LayoutInflater.from(context);
          }

          public?LayoutInflater?getLayoutInflater()?{
          ????return?mLayoutInflater;
          }
          • 4. LayoutInflater#from(Context)
          public?static?LayoutInflater?from(Context?context)?{
          ????LayoutInflater?LayoutInflater?=?(LayoutInflater)?context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
          ????if?(LayoutInflater?==?null)?{
          ????????throw?new?AssertionError("LayoutInflater?not?found.");
          ????}
          ????return?LayoutInflater;
          }

          可以看到,前面 3 種方法最后走到LayoutInflater#from(Context),這其實也是平時用的最多的方式。現(xiàn)在,我們看getSystemService(...)內的邏輯:

          ContextImpl.java

          @Override
          public?Object?getSystemService(String?name)?{
          ????return?SystemServiceRegistry.getSystemService(this,?name);
          }

          SystemServiceRegistry.java

          private?static?final?Map>?SYSTEM_SERVICE_FETCHERS?=?new?ArrayMap>();

          static?{
          ????...
          ????1.?注冊?Context.LAYOUT_INFLATER_SERVICE?與服務獲取器
          ????關注點:CachedServiceFetcher
          ????關注點:PhoneLayoutInflater
          ????registerService(Context.LAYOUT_INFLATER_SERVICE,?LayoutInflater.class,?new?CachedServiceFetcher()?{
          ????????@Override
          ????????public?LayoutInflater?createService(ContextImpl?ctx)?{
          ????????????注意:getOuterContext(),參數(shù)使用的是 ContextImpl 的代理對象,一般是 Activity
          ????????????return?new?PhoneLayoutInflater(ctx.getOuterContext());
          ????????}});
          ????...
          }

          2.?根據?name?獲取服務對象
          public?static?Object?getSystemService(ContextImpl?ctx,?String?name)?{
          ????ServiceFetcher?fetcher?=?SYSTEM_SERVICE_FETCHERS.get(name);
          ????return?fetcher?!=?null???fetcher.getService(ctx)?:?null;
          }

          注冊服務與服務獲取器
          private?static??void?registerService(String?serviceName,?Class?serviceClass,?ServiceFetcher?serviceFetcher)?{
          ????SYSTEM_SERVICE_FETCHERS.put(serviceName,?serviceFetcher);
          }

          3.?服務獲取器創(chuàng)建對象
          static?abstract?interface?ServiceFetcher?{
          ????T?getService(ContextImpl?ctx);
          }

          可以看到,ContextImpl 內部通過 SystemServiceRegistry 來獲取服務對象,邏輯并不復雜:

          1、靜態(tài)代碼塊注冊了 name - ServiceFetcher 的映射 2、根據 name 獲得 ServiceFetcher 3、ServiceFetcher 創(chuàng)建對象

          ServiceFetcher 的子類有三種類型,它們的getSystemService()都是線程安全的,主要差別體現(xiàn)在 單例范圍,具體如下:

          ServiceFetcher子類單例范圍描述舉例
          CachedServiceFetcherContextImpl域/LayoutInflater、LocationManager等(最多)
          StaticServiceFetcher進程域/InputManager、JobScheduler等
          StaticApplicationContextServiceFetcher進程域使用 ApplicationContext 創(chuàng)建服務ConnectivityManager

          對于 LayoutInflater 來說,服務獲取器是 CachedServiceFetcher 的子類,最終獲得的服務對象為 PhoneLayoutInflater。

          這里有一個重點,這句代碼非常隱蔽,要留意:

          ?return?new?PhoneLayoutInflater(ctx.getOuterContext());

          LayoutInflater.java

          public?Context?getContext()?{
          ????return?mContext;?
          }

          protected?LayoutInflater(Context?context)?{
          ????mContext?=?context;
          ????initPrecompiledViews();
          }

          可以看到,實例化 PhoneLayoutInflater 時使用了 getOuterContext(),也就是參數(shù)使用的是 ContextImpl 的代理對象,一般就是 Activity 了。也就是說,在 Activity / Fragment / View / Dialog 中,獲取LayoutInflater#getContext(),返回的就是 Activity。

          小結:

          • 1、獲取 LayoutInflater 對象只有通過LayoutInflater.from(context),內部委派給Context#getSystemService(...),線程安全;
          • 2、使用同一個 Context 對象,獲得的 LayoutInflater 是單例;
          • 3、LayoutInflater 的實現(xiàn)類是 PhoneLayoutInflater。

          2. inflate(...) 主流程源碼分析

          上一節(jié),我們分析了獲取 LayoutInflater 對象的過程,現(xiàn)在我們可以調用inflate()進行布局解析了。LayoutInflater#inflate(...)有多個重載方法,最終都會調用到:

          public?View?inflate(@LayoutRes?int?resource,?@Nullable?ViewGroup?root,?boolean?attachToRoot)?{
          ????final?Resources?res?=?getContext().getResources();
          ????1.?解析預編譯的布局
          ????View?view?=?tryInflatePrecompiled(resource,?res,?root,?attachToRoot);
          ????if?(view?!=?null)?{
          ????????return?view;
          ????}
          ????2.?構造?XmlPull?解析器?
          ????XmlResourceParser?parser?=?res.getLayout(resource);
          ????try?{
          ????3.?執(zhí)行解析
          ????????return?inflate(parser,?root,?attachToRoot);
          ????}?finally?{
          ????????parser.close();
          ????}
          }
          1. tryInflatePrecompiled(...)是解析預編譯的布局,我后文再說;
          2. 構造 XmlPull 解析器 XmlResourceParser
          3. 執(zhí)行解析,是解析的主流程

          提示: 在這里,我剔除了與 XmlPull 相關的代碼,只保留了我們關心的邏輯:

          public?View?inflate(XmlPullParser?parser,?@Nullable?ViewGroup?root,?boolean?attachToRoot)?{
          ????1.?結果變量
          ????View?result?=?root;
          ????2.?最外層的標簽
          ????final?String?name?=?parser.getName();
          ????3.?
          ????if?(TAG_MERGE.equals(name))?{
          ????????3.1?異常
          ????????if?(root?==?null?||?!attachToRoot)?{
          ????????????throw?new?InflateException("?can?be?used?only?with?a?valid?"
          ????????????????+?"ViewGroup?root?and?attachToRoot=true");
          ????????}
          ????????3.2?遞歸執(zhí)行解析
          ????????rInflate(parser,?root,?inflaterContext,?attrs,?false);
          ????}?else?{
          ????????4.1?創(chuàng)建最外層?View
          ????????final?View?temp?=?createViewFromTag(root,?name,?inflaterContext,?attrs);
          ????????
          ????????ViewGroup.LayoutParams?params?=?null;

          ????????if?(root?!=?null)?{
          ????????????4.2?創(chuàng)建匹配的?LayoutParams
          ????????????params?=?root.generateLayoutParams(attrs);
          ????????????if?(!attachToRoot)?{
          ????????????????4.3?如果?attachToRoot?為?false,設置LayoutParams
          ????????????????temp.setLayoutParams(params);
          ????????????}
          ????????}

          ????????5.?以?temp?為?root,遞歸執(zhí)行解析
          ????????rInflateChildren(parser,?temp,?attrs,?true);
          ????????
          ????????6.?attachToRoot?為?true,addView()
          ????????if?(root?!=?null?&&?attachToRoot)?{
          ????????????root.addView(temp,?params);
          ????????}

          ????????7.?root?為空?或者?attachToRoot?為?false,返回?temp
          ????????if?(root?==?null?||?!attachToRoot)?{
          ????????????result?=?temp;
          ????????}
          ????}
          ????return?result;
          }

          ->?3.2
          void?rInflate(XmlPullParser?parser,?View?parent,?Context?context,?AttributeSet?attrs,?boolean?finishInflate)?{
          ????while(parser?未結束)?{
          ????????if?(TAG_INCLUDE.equals(name))?{
          ????????????1)?
          ????????????if?(parser.getDepth()?==?0)?{
          ????????????????throw?new?InflateException("?cannot?be?the?root?element");
          ????????????}
          ????????????parseInclude(parser,?context,?parent,?attrs);
          ????????}?else?if?(TAG_MERGE.equals(name))?{
          ????????????2)?
          ????????????throw?new?InflateException("?must?be?the?root?element");
          ????????}?else?{
          ????????????3)?創(chuàng)建?View?
          ????????????final?View?view?=?createViewFromTag(parent,?name,?context,?attrs);
          ????????????final?ViewGroup?viewGroup?=?(ViewGroup)?parent;
          ????????????final?ViewGroup.LayoutParams?params?=?viewGroup.generateLayoutParams(attrs);
          ????????????4)?遞歸
          ????????????rInflateChildren(parser,?view,?attrs,?true);
          ????????????5)?添加到視圖樹
          ????????????viewGroup.addView(view,?params);
          ????????}
          ????}
          }

          ->?5.?遞歸執(zhí)行解析
          final?void?rInflateChildren(XmlPullParser?parser,?View?parent,?AttributeSet?attrs,
          ????????????boolean?finishInflate)?throws?XmlPullParserException,?IOException?{
          ????rInflate(parser,?parent,?parent.getContext(),?attrs,?finishInflate);
          }

          關于 & ,我后文再說。對于參數(shù) root & attachToRoot的不同情況,對應得到的輸出不同,我總結為一張圖:


          3. createViewFromTag():從到 View

          第 2 節(jié) 主流程代碼中,用到了 createViewFromTag(),它負責由創(chuàng)建 View 對象:

          已簡化
          View?createViewFromTag(View?parent,?String?name,?Context?context,?AttributeSet?attrs,?boolean?ignoreThemeAttr)?{

          ????1.?應用?ContextThemeWrapper?以支持?android:theme
          ????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();
          ????}

          ????2.?先使用?Factory2?/?Factory?實例化?View,相當于攔截
          ????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;
          ????}

          ????3.?使用?mPrivateFactory?實例化?View,相當于攔截
          ????if?(view?==?null?&&?mPrivateFactory?!=?null)?{
          ????????view?=?mPrivateFactory.onCreateView(parent,?name,?context,?attrs);
          ????}

          ????4.?調用自身邏輯
          ????if?(view?==?null)?{
          ????????if?(-1?==?name.indexOf('.'))?{
          ????????????4.1??中沒有.
          ????????????view?=?onCreateView(parent,?name,?attrs);
          ????????}?else?{
          ????????????4.2??中有.
          ????????????view?=?createView(name,?null,?attrs);
          ????????}
          ????}
          ????return?view;?????
          }

          ->?4.2??中有.

          構造器方法簽名
          static?final?Class[]?mConstructorSignature?=?new?Class[]?{
          ????????????Context.class,?AttributeSet.class};

          緩存?View?構造器的?Map
          private?static?final?HashMap>?sConstructorMap?=
          ????????????new?HashMap>();

          public?final?View?createView(String?name,?String?prefix,?AttributeSet?attrs)?{
          ????1)?緩存的構造器
          ????Constructor?constructor?=?sConstructorMap.get(name);
          ????if?(constructor?!=?null?&&?!verifyClassLoader(constructor))?{
          ????????constructor?=?null;
          ????????sConstructorMap.remove(name);
          ????}
          ????Class?clazz?=?null;
          ????2)?新建構造器
          ????if?(constructor?==?null)?{
          ????????2.1)?拼接?prefix?+?name?得到類全限定名
          ????????clazz?=?mContext.getClassLoader().loadClass(prefix?!=?null???(prefix?+?name)?:?name).asSubclass(View.class);
          ????????2.2)?創(chuàng)建構造器對象
          ????????constructor?=?clazz.getConstructor(mConstructorSignature);
          ????????constructor.setAccessible(true);
          ????????2.3)?緩存到?Map
          ????????sConstructorMap.put(name,?constructor);
          ????}
          ????
          ????3)?實例化?View?對象
          ????final?View?view?=?constructor.newInstance(args);

          ????4)?ViewStub?特殊處理
          ????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;
          }

          ----------------------------------------------------

          ->?4.1??中沒有.

          PhoneLayoutInflater.java

          private?static?final?String[]?sClassPrefixList?=?{
          ????"android.widget.",
          ????"android.webkit.",
          ????"android.app."
          };

          已簡化
          protected?View?onCreateView(String?name,?AttributeSet?attrs)?{
          ????for?(String?prefix?:?sClassPrefixList)?{
          ????????View?view?=?createView(name,?prefix,?attrs);
          ????????????if?(view?!=?null)?{
          ????????????????return?view;
          ????????????}
          ????}
          ????return?super.onCreateView(name,?attrs);
          }

            1. 應用 ContextThemeWrapper 以支持android:theme,這是處理針對特定 View 設置主題;

            1. 使用 Factory2 / Factory 實例化 View,相當于攔截,我后文再說;

            1. 使用 mPrivateFactory 實例化View,相當于攔截,我后文再說;
          • 1)?緩存的構造器
            2)?新建構造器
            3)?實例化?View?對象
            4)?ViewStub?特殊處理
            • 4.1中沒有.,這是處理、等標簽,依次嘗試拼接 3 個路徑前綴,進入 3.2 實例化 View
            • 4.2中有.,真正實例化 View 的地方,主要分為 4 步:
            1. 調用 LayoutInflater 自身邏輯,分為:

          小結:

          • 使用 Factory2 接口可以攔截實例化 View 對象的步驟;
          • 實例化 View 的優(yōu)先順序為:Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;
          • 使用反射實例化 View 對象,同時構造器對象做了緩存;

          4. Factory2 接口

          現(xiàn)在我們來討論Factory2接口,上一節(jié)提到,Factory2可以攔截實例化 View 的步驟,在 LayoutInflater 中有兩個方法可以設置:LayoutInflater.java

          方法1:
          public?void?setFactory2(Factory2?factory)?{
          ????if?(mFactorySet)?{
          ????????關注點:禁止重復設置
          ????????throw?new?IllegalStateException("A?factory?has?already?been?set?on?this?LayoutInflater");
          ????}
          ????if?(factory?==?null)?{
          ????????throw?new?NullPointerException("Given?factory?can?not?be?null");
          ????}
          ????mFactorySet?=?true;
          ????if?(mFactory?==?null)?{
          ????????mFactory?=?mFactory2?=?factory;
          ????}?else?{
          ????????mFactory?=?mFactory2?=?new?FactoryMerger(factory,?factory,?mFactory,?mFactory2);
          ????}
          }

          方法2?@hide
          public?void?setPrivateFactory(Factory2?factory)?{
          ????if?(mPrivateFactory?==?null)?{
          ????????mPrivateFactory?=?factory;
          ????}?else?{
          ????????mPrivateFactory?=?new?FactoryMerger(factory,?factory,?mPrivateFactory,?mPrivateFactory);
          ????}
          }

          現(xiàn)在,我們來看源碼中哪里調用這兩個方法:

          4.1 setFactory2()

          在 AppCompatActivity & AppCompatDialog 中,相關源碼簡化如下:

          AppCompatDialog.java

          @Override
          protected?void?onCreate(Bundle?savedInstanceState)?{
          ????設置?Factory2
          ????getDelegate().installViewFactory();
          ????super.onCreate(savedInstanceState);
          ????getDelegate().onCreate(savedInstanceState);
          }

          AppCompatActivity.java

          @Override
          protected?void?onCreate(@Nullable?Bundle?savedInstanceState)?{
          ????final?AppCompatDelegate?delegate?=?getDelegate();
          ????設置?Factory2
          ????delegate.installViewFactory();
          ????delegate.onCreate(savedInstanceState);
          ????夜間主題相關
          ????if?(delegate.applyDayNight()?&&?mThemeId?!=?0)?{
          ????????if?(Build.VERSION.SDK_INT?>=?23)?{
          ????????????onApplyThemeResource(getTheme(),?mThemeId,?false);
          ????????}?else?{
          ????????????setTheme(mThemeId);
          ????????}
          ????}
          ????super.onCreate(savedInstanceState);
          }

          AppCompatDelegateImpl.java

          public?void?installViewFactory()?{
          ????LayoutInflater?layoutInflater?=?LayoutInflater.from(mContext);
          ????if?(layoutInflater.getFactory()?==?null)?{
          ????????關注點:設置 Factory2 = this(AppCompatDelegateImpl)
          ????????LayoutInflaterCompat.setFactory2(layoutInflater,?this);
          ????}?else?{
          ????????if?(!(layoutInflater.getFactory2()?instanceof?AppCompatDelegateImpl))?{
          ????????????Log.i(TAG,?"The?Activity's?LayoutInflater?already?has?a?Factory?installed"
          ????????????????????????+?"?so?we?can?not?install?AppCompat's");
          ????????}
          ????}
          }

          LayoutInflaterCompat.java

          public?static?void?setFactory2(@NonNull?LayoutInflater?inflater,?@NonNull?LayoutInflater.Factory2?factory)?{
          ????inflater.setFactory2(factory);

          ????if?(Build.VERSION.SDK_INT?????????final?LayoutInflater.Factory?f?=?inflater.getFactory();
          ????????if?(f?instanceof?LayoutInflater.Factory2)?{
          ????????????forceSetFactory2(inflater,?(LayoutInflater.Factory2)?f);
          ????????}?else?{
          ????????????forceSetFactory2(inflater,?factory);
          ????????}
          ????}
          }

          可以看到,在 AppCompatDialog & AppCompatActivity 初始化時,都通過setFactory2()設置了攔截器,設置的對象是 AppCompatDelegateImpl:

          AppCompatDelegateImpl.java

          已簡化
          class?AppCompatDelegateImpl?extends?AppCompatDelegate
          ????????implements?MenuBuilder.Callback,?LayoutInflater.Factory2?{

          ????@Override
          ????public?final?View?onCreateView(View?parent,?String?name,?Context?context,?AttributeSet?attrs)?{
          ????????return?createView(parent,?name,?context,?attrs);
          ????}

          ????@Override
          ????public?View?createView(View?parent,?final?String?name,?@NonNull?Context?context,
          ????????????@NonNull?AttributeSet?attrs)?{
          ????????if?(mAppCompatViewInflater?==?null)?{
          ????????????mAppCompatViewInflater?=?new?AppCompatViewInflater();
          ????????}
          ????}
          ????委托給?AppCompatViewInflater?處理
          ????return?mAppCompatViewInflater.createView(...)
          }

          AppCompatViewInflater ?與 LayoutInflater 的核心流程差不多,主要差別是前者會等標簽解析為AppCompatTextView對象:

          AppCompatViewInflater.java

          final?View?createView(...)?{
          ????...

          ????switch?(name)?{
          ????????case?"TextView":
          ????????????view?=?createTextView(context,?attrs);
          ????????????break;
          ????????...
          ????????default:
          ????????????view?=?createView(context,?name,?attrs);
          ????}
          ????return?view;
          }

          @NonNull
          protected?AppCompatTextView?createTextView(Context?context,?AttributeSet?attrs)?{
          ????return?new?AppCompatTextView(context,?attrs);
          }

          4.2 setPrivateFactory()

          setPrivateFactory()是 hide 方法,在 Activity 中調用,相關源碼簡化如下:

          Activity.java

          final?FragmentController?mFragments?=?FragmentController.createController(new?HostCallbacks());

          final?void?attach(Context?context,?ActivityThread?aThread,...)?{
          ????attachBaseContext(context);
          ????mFragments.attachHost(null?/*parent*/);

          ????mWindow?=?new?PhoneWindow(this,?window,?activityConfigCallback);
          ????mWindow.setWindowControllerCallback(this);
          ????mWindow.setCallback(this);
          ????mWindow.setOnWindowDismissedCallback(this);
          ????關注點:設置 Factory2
          ????mWindow.getLayoutInflater().setPrivateFactory(this);
          ????...
          }

          可以看到,這里設置的 Factory2 其實就是 Activity 本身(this),這說明 Activity 也實現(xiàn)了 Factory2 :

          public?class?Activity?extends?ContextThemeWrapper?implements?LayoutInflater.Factory2,...{

          ????public?View?onCreateView(View?parent,?String?name,?Context?context,?AttributeSet?attrs)?{
          ????????if?(!"fragment".equals(name))?{
          ????????????return?onCreateView(name,?context,?attrs);
          ????????}

          ????????return?mFragments.onCreateView(parent,?name,?context,?attrs);
          ????}
          }

          原來標簽的處理是在這里設置的 Factory2 處理的,關于FragmentController#onCreateView(...)內部如何生成 Fragment 以及返回 View 的邏輯,我們在這篇文章里討論,請關注:《Android | 考點爆滿,帶你全方位圖解 Fragment 源碼》。

          小結:

          • 使用 setFactory2() 和 setPrivateFactory() 可以設置 Factory2 接口(攔截器),其中同一個 LayoutInflater 的setFactory2()不能重復設置,setPrivateFactory() 是 hide 方法;
          • AppCompatDialog & AppCompatActivity 初始化時,調用了setFactory2(),會將一些轉換為AppCompat版本;
          • Activity 初始化時,調用了setPrivateFactory(),用來處理標簽。

          5. & &

          這一節(jié),我們專門來討論 & & 的用法與注意事項:

          5.1 布局重用

          5.2 降低布局層次

          5.3 布局懶加載

          Editting...


          6. 總結

          • 應試建議
          1. 理解 獲取 LayoutInflater 對象的方式,知曉 3 種 getSystemService() 單例域的區(qū)別,其中 Context 域是最多的,LayoutInflater 是屬于 Context 域。
          2. 重點理解 LayoutInflater 布局解析的 核心流程;
          3. Factory2 是一個很實用的接口,需要掌握通過 setFactory2() 攔截布局解析 的技巧。

          歡迎關注彭旭銳的掘金!https://juejin.im/user/1063982987230392
          瀏覽 62
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩无日韩欧美 | 91成人精品在线视频 | 国产ts一区 | 91丝袜视频 | 色婷婷一区二区 |