<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組件化核心!

          共 1805字,需瀏覽 4分鐘

           ·

          2022-04-16 17:49

          ?BATcoder技術(shù)群,讓一部分人先進(jìn)大廠

          大家好,我是劉望舒,騰訊最具價(jià)值專家,著有三本業(yè)內(nèi)知名暢銷書,連續(xù)五年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,百度百科收錄的資深技術(shù)專家。

          前華為面試官、獨(dú)角獸公司技術(shù)總監(jiān)。


          想要加入?BATcoder技術(shù)群,公號(hào)回復(fù)BAT?即可。

          作者:看書的小蝸牛?

          https://www.jianshu.com/p/e7bbe365ebc1


          前端開發(fā)經(jīng)常遇到一個(gè)詞:路由,在Android APP開發(fā)中,路由還經(jīng)常和組件化開發(fā)強(qiáng)關(guān)聯(lián)在一起,那么到底什么是路由,一個(gè)路由框架到底應(yīng)該具備什么功能,實(shí)現(xiàn)原理是什么樣的?路由是否是APP的強(qiáng)需求呢?與組件化到底什么關(guān)系,本文就簡(jiǎn)單分析下如上幾個(gè)問題。

          1.路由的概念

          路由這個(gè)詞本身應(yīng)該是互聯(lián)網(wǎng)協(xié)議中的一個(gè)詞,維基百科對(duì)此的解釋如下:


          路由(routing)就是通過互聯(lián)的網(wǎng)絡(luò)把信息從源地址傳輸?shù)侥康牡刂返幕顒?dòng)。路由發(fā)生在OSI網(wǎng)絡(luò)參考模型中的第三層即網(wǎng)絡(luò)層。


          個(gè)人理解,在前端開發(fā)中,路由就是通過一串字符串映射到對(duì)應(yīng)業(yè)務(wù)的能力。APP的路由框首先能夠搜集各組件的路由scheme,并生成路由表,然后,能夠根據(jù)外部輸入字符串在路由表中匹配到對(duì)應(yīng)的頁(yè)面或者服務(wù),進(jìn)行跳轉(zhuǎn)或者調(diào)用,并提供會(huì)獲取返回值等,示意如下:



          所以一個(gè)基本路由框架要具備如下能力:


          1. APP路由的掃描及注冊(cè)邏輯。


          2. ?路由跳轉(zhuǎn)target頁(yè)面能力。


          3. 路由調(diào)用target服務(wù)能力。


          APP中,在進(jìn)行頁(yè)面路由的時(shí)候,經(jīng)常需要判斷是否登錄等一些額外鑒權(quán)邏輯所以,還需要提供攔截邏輯等,比如:登陸。

          2.三方路由框架是否是APP強(qiáng)需求

          答案:不是,系統(tǒng)原生提供路由能力,但功能較少,稍微大規(guī)模的APP都采用三方路由框架。


          Android系統(tǒng)本身提供頁(yè)面跳轉(zhuǎn)能力:如startActivity,對(duì)于工具類APP,或單機(jī)類APP,這種方式已經(jīng)完全夠用,完全不需要專門的路由框架,那為什么很多APP還是采用路由框架呢?這跟APP性質(zhì)及路由框架的優(yōu)點(diǎn)都有關(guān)。比如淘寶、京東、美團(tuán)等這些大型APP,無論是從APP功能還是從其研發(fā)團(tuán)隊(duì)的規(guī)模上來說都很龐大,不同的業(yè)務(wù)之間也經(jīng)常是不同的團(tuán)隊(duì)在維護(hù),采用組件化的開發(fā)方式,最終集成到一個(gè)APK中。


          多團(tuán)隊(duì)之間經(jīng)常會(huì)涉及業(yè)務(wù)間的交互,比如從電影票業(yè)務(wù)跳轉(zhuǎn)到美食業(yè)務(wù),但是兩個(gè)業(yè)務(wù)是兩個(gè)獨(dú)立的研發(fā)團(tuán)隊(duì),代碼實(shí)現(xiàn)上是完全隔離的,那如何進(jìn)行通信呢?首先想到的是代碼上引入,但是這樣會(huì)打破了低耦合的初衷,可能還會(huì)引入各種問題。


          例如,部分業(yè)務(wù)是外包團(tuán)隊(duì)來做,這就牽扯到代碼安全問題,所以還是希望通過一種類似黑盒的方式,調(diào)用目標(biāo)業(yè)務(wù),這就需要中轉(zhuǎn)路由支持,所以國(guó)內(nèi)很多APP都是用了路由框架的。其次我們各種跳轉(zhuǎn)的規(guī)則并不想跟具體的實(shí)現(xiàn)類扯上關(guān)系,比如跳轉(zhuǎn)商詳?shù)臅r(shí)候,不希望知道是哪個(gè)Activity來實(shí)現(xiàn),只需要一個(gè)字符串映射過去即可,這對(duì)于H5、或者后端開發(fā)來處理跳轉(zhuǎn)的時(shí)候,就非常標(biāo)準(zhǔn)。

          3.原生路由的限制:功能單一,擴(kuò)展靈活性差,不易協(xié)同


          傳統(tǒng)的路由基本上就限定在startActivity、或者startService來路由跳轉(zhuǎn)或者啟動(dòng)服務(wù)。拿startActivity來說,傳統(tǒng)的路由有什么缺點(diǎn):startActivity有兩種用法,一種是顯示的,一種是隱式的,顯示調(diào)用如下:



          import?com.snail.activityforresultexample.test.SecondActivity;

          public?class?MainActivity?extends?AppCompatActivity?{

          ????void?jumpSecondActivityUseClassName(){
          ????
          ????????Intent?intent?=new?Intent(MainActivity.this,?SecondActivity.class);
          ????????startActivity(intent);
          ????}


          顯示調(diào)用的缺點(diǎn)很明顯,那就是必須要強(qiáng)依賴目標(biāo)Activity的類實(shí)現(xiàn),有些場(chǎng)景,尤其是大型APP組件化開發(fā)時(shí)候,有些業(yè)務(wù)邏輯出于安全考慮,并不想被源碼或aar依賴,這時(shí)顯式依賴的方式就無法走通。再來看看隱式調(diào)用方法。


          第一步:manifest中配置activity的intent-filter,至少要配置一個(gè)action。



          <manifest?xmlns:android="http://schemas.android.com/apk/res/android"
          ????package="com.snail.activityforresultexample">

          ????<application
          ???????...
          ????<activity?android:name=".test.SecondActivity">

          ????????????<intent-filter>
          ????????????
          ???????????????????<category?android:name="android.intent.category.DEFAULT"/>
          ????????????
          ????????????????<action?android:name="com.snail.activityforresultexample.SecondActivity"?/>
          ????????????????
          ??
          ????????????intent-filter>

          ????????activity>
          ????application>
          manifest>


          第二步:調(diào)用。


          void?jumpSecondActivityUseFilter()?{
          ????Intent?intent?=?new?Intent();
          ????intent.setAction("com.snail.activityforresultexample.SecondActivity");
          ????startActivity(intent);
          }


          如果牽扯到數(shù)據(jù)傳遞寫法上會(huì)更復(fù)雜一些,隱式調(diào)用的缺點(diǎn)有如下幾點(diǎn):


          首先manifest中定義復(fù)雜,相對(duì)應(yīng)的會(huì)導(dǎo)致暴露的協(xié)議變的復(fù)雜,不易維護(hù)擴(kuò)展。


          其次,不同Activity都要不同的action配置,每次增減修改Activity都會(huì)很麻煩,對(duì)比開發(fā)者非常不友好,增加了協(xié)作難度。


          最后,Activity的export屬性并不建議都設(shè)置成True,這是降低風(fēng)險(xiǎn)的一種方式,一般都是收歸到一個(gè)Activity,DeeplinkActivitiy統(tǒng)一處理跳轉(zhuǎn),這種場(chǎng)景下,DeeplinkActivitiy就兼具路由功能,隱式調(diào)用的場(chǎng)景下,新Activitiy的增減勢(shì)必每次都要調(diào)整路由表,這會(huì)導(dǎo)致開發(fā)效率降低,風(fēng)險(xiǎn)增加。


          可以看到系統(tǒng)原生的路由框架,并沒太多考慮團(tuán)隊(duì)協(xié)同的開發(fā)模式,多限定在一個(gè)模塊內(nèi)部多個(gè)業(yè)務(wù)間直接相互引用,基本都要代碼級(jí)依賴,對(duì)于代碼及業(yè)務(wù)隔離很不友好。如不考慮之前Dex方法樹超限制,可以認(rèn)為三方路由框架完全是為了團(tuán)隊(duì)協(xié)同而創(chuàng)建的。

          4.APP三方路由框架需具備的能力

          目前市面上大部分的路由框架都能搞定上述問題,簡(jiǎn)單整理下現(xiàn)在三方路由的能力,可歸納如下:


          路由表生成能力:業(yè)務(wù)組件[UI業(yè)務(wù)及服務(wù)]自動(dòng)掃描及注冊(cè)邏輯,需要擴(kuò)展性好,無需入侵原有代碼邏輯。


          scheme與業(yè)務(wù)映射邏輯 :無需依賴具體實(shí)現(xiàn),做到代碼隔離。


          基礎(chǔ)路由跳轉(zhuǎn)能力 :頁(yè)面跳轉(zhuǎn)能力的支持。


          服務(wù)類組件的支持 :如去某個(gè)服務(wù)組件獲取一些配置等。


          [擴(kuò)展]路由攔截邏輯:比如登陸,統(tǒng)一鑒權(quán)。


          可定制的降級(jí)邏輯:找不到組件時(shí)的兜底。


          可以看下一個(gè)典型的Arouter用法,第一步:對(duì)新增頁(yè)面添加Router Scheme 聲明。

          ? ?

          @Route(path?=?"/test/activity2")
          public?class?Test2Activity?extends?AppCompatActivity?{
          ?????...
          }


          build階段會(huì)根據(jù)注解搜集路由scheme,生成路由表。第二步使用:


          ARouter.getInstance()
          ????????.build("/test/activity2")
          ????????.navigation(this);


          如上,在ARouter框架下,僅需要字符串scheme,無需依賴任何Test2Activity就可實(shí)現(xiàn)路由跳轉(zhuǎn)。

          5.APP路由框架的實(shí)現(xiàn)

          路由框架實(shí)現(xiàn)的核心是建立scheme和組件[Activity或者其他服務(wù)]的映射關(guān)系,也就是路由表,并能根據(jù)路由表路由到對(duì)應(yīng)組件的能力。其實(shí)分兩部分,第一部分路由表的生成,第二部分,路由表的查詢。


          路由表的自動(dòng)生成


          生成路由表的方式有很多,最簡(jiǎn)單的就是維護(hù)一個(gè)公共文件或者類,里面映射好每個(gè)實(shí)現(xiàn)組件跟scheme。



          不過,這種做法缺點(diǎn)很明顯:每次增刪修改都要都要修改這個(gè)表,對(duì)于協(xié)同非常不友好,不符合解決協(xié)同問題的初衷。不過,最終的路由表倒是都是這條路,就是將所有的Scheme搜集到一個(gè)對(duì)象中,只是實(shí)現(xiàn)方式的差別,目前幾乎所有的三方路由框架都是借助注解+APT[Annotation Processing Tool]工具+AOP(Aspect-Oriented Programming,面向切面編程)來實(shí)現(xiàn)的,基本流程如下:



          其中牽扯的技術(shù)有注解、APT(Annotation Processing Tool)、AOP(Aspect-Oriented Programming,面向切面編程)。APT常用的有JavaPoet,主要是遍歷所有類,找到被注解的Java類,然后聚合生成路由表,由于組件可能有很多,路由表可能也有也有多個(gè),之后,這些生成的輔助類會(huì)跟源碼一并被編譯成class文件,之后利用AOP技術(shù)【如ASM或者JavaAssist】,掃描這些生成的class,聚合路由表,并填充到之前的占位方法中,完成自動(dòng)注冊(cè)的邏輯。


          JavaPoet如何搜集并生成路由表集合?


          以ARouter框架為例,先定義Router框架需要的注解如:


          @Target({ElementType.TYPE})
          @Retention(RetentionPolicy.CLASS)
          public?@interface?Route?{

          ????/**
          ?????*?Path?of?route
          ?????*/

          ????String?path();


          該注解用于標(biāo)注需要路由的組件,用法如下:


          @Route(path?=?"/test/activity1",?name?=?"測(cè)試用?Activity")
          public?class?Test1Activity?extends?BaseActivity?{
          ????@Autowired
          ????int?age?=?10;


          之后利用APT掃描所有被注解的類,生成路由表,實(shí)現(xiàn)參考如下:


          @Override
          public?boolean?process(Set?annotations,?RoundEnvironment?roundEnv)?{
          ????if?(CollectionUtils.isNotEmpty(annotations))?{
          ????
          ????????Set?routeElements?=?roundEnv.getElementsAnnotatedWith(Route.class);
          ????????
          ????????????this.parseRoutes(routeElements);
          ???????...
          ????return?false;
          }

          ?
          private?void?parseRoutes(Set?extends?Element>?routeElements)?throws?IOException?{
          ????????????????????????...
          ?????????????????????//?Generate?groups
          ????????????String?groupFileName?=?NAME_OF_GROUP?+?groupName;
          ????????????JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
          ????????????????????TypeSpec.classBuilder(groupFileName)
          ????????????????????????????.addJavadoc(WARNING_TIPS)
          ????????????????????????????.addSuperinterface(ClassName.get(type_IRouteGroup))
          ????????????????????????????.addModifiers(PUBLIC)
          ????????????????????????????.addMethod(loadIntoMethodOfGroupBuilder.build())
          ????????????????????????????.build()
          ????????????).build().writeTo(mFiler);


          產(chǎn)物如下:包含路由表,及局部注冊(cè)入口。



          自動(dòng)注冊(cè):ASM搜集上述路由表并聚合插入Init代碼區(qū)。


          為了能夠插入到Init代碼區(qū),首先需要預(yù)留一個(gè)位置,一般定義一個(gè)空函數(shù),以待后續(xù)填充:

          ? ?

          public?class?RouterInitializer?{

          ????public?static?void?init(boolean?debug,?Class?webActivityClass,?IRouterInterceptor...?interceptors)?{
          ????????...
          ????????loadRouterTables();
          ????}
          ????//自動(dòng)注冊(cè)代碼????
          ????public?static?void?loadRouterTables()?{

          ????}
          }


          首先利用AOP工具,遍歷上述APT中間產(chǎn)物,聚合路由表,并注冊(cè)到預(yù)留初始化位置,遍歷的過程牽扯是gradle transform的過程。


          搜集目標(biāo),聚合路由表


          /**掃描jar*/
          fun?scanJar(jarFile:?File,?dest:?File?)?{

          ????val?file?=?JarFile(jarFile)
          ????var?enumeration?=?file.entries()
          ????while?(enumeration.hasMoreElements())?{
          ????????val?jarEntry?=?enumeration.nextElement()
          ????????if?(jarEntry.name.endsWith("XXRouterTable.class"))?{
          ????????????val?inputStream?=?file.getInputStream(jarEntry)
          ????????????val?classReader?=?ClassReader(inputStream)
          ????????????if?(Arrays.toString(classReader.interfaces)
          ????????????????????.contains("IHTRouterTBCollect")
          ????????????)?{
          ????????????????tableList.add(
          ????????????????????Pair(
          ????????????????????????classReader.className,
          ????????????????????????dest?.absolutePath
          ????????????????????)
          ????????????????)
          ????????????}
          ????????????inputStream.close()
          ????????}?else?if?(jarEntry.name.endsWith("HTRouterInitializer.class"))?{
          ????????????registerInitClass?=?dest
          ????????}
          ????}
          ????file.close()
          }


          對(duì)目標(biāo)Class注入路由表初始化代碼


          fun?asmInsertMethod(originFile:?File?)?{

          ????val?optJar?=?File(originFile?.parent,?originFile?.name?+?".opt")
          ????if?(optJar.exists())
          ????????optJar.delete()
          ????val?jarFile?=?JarFile(originFile)
          ????val?enumeration?=?jarFile.entries()
          ????val?jarOutputStream?=?JarOutputStream(FileOutputStream(optJar))

          ????while?(enumeration.hasMoreElements())?{
          ????????val?jarEntry?=?enumeration.nextElement()
          ????????val?entryName?=?jarEntry.getName()
          ????????val?zipEntry?=?ZipEntry(entryName)
          ????????val?inputStream?=?jarFile.getInputStream(jarEntry)
          ????????//插樁class
          ????????if?(entryName.endsWith("RouterInitializer.class"))?{
          ????????????//class文件處理
          ????????????jarOutputStream.putNextEntry(zipEntry)
          ????????????val?classReader?=?ClassReader(IOUtils.toByteArray(inputStream))
          ????????????val?classWriter?=?ClassWriter(classReader,?ClassWriter.COMPUTE_MAXS)
          ????????????val?cv?=?RegisterClassVisitor(Opcodes.ASM5,?classWriter,tableList)
          ????????????classReader.accept(cv,?EXPAND_FRAMES)
          ????????????val?code?=?classWriter.toByteArray()
          ????????????jarOutputStream.write(code)
          ????????}?else?{
          ????????????jarOutputStream.putNextEntry(zipEntry)
          ????????????jarOutputStream.write(IOUtils.toByteArray(inputStream))
          ????????}
          ????????jarOutputStream.closeEntry()
          ????}
          ????//結(jié)束
          ????jarOutputStream.close()
          ????jarFile.close()
          ????if?(originFile?.exists()?==?true)?{
          ????????Files.delete(originFile.toPath())
          ????}
          ????optJar.renameTo(originFile)
          }


          最終RouterInitializer.classloadRouterTables會(huì)被修改成如下填充好的代碼:


          public?static?void?loadRouterTables()?{

          ??
          ??register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
          ??register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin");
          ??register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
          ??register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
          ??...
          }


          如此就完成了路由表的搜集與注冊(cè),大概的流程就是如此。當(dāng)然對(duì)于支持服務(wù)、Fragment等略有不同,但大體類似。


          Router框架對(duì)服務(wù)類組件的支持


          通過路由的方式獲取服務(wù)屬于APP路由比較獨(dú)特的能力,比如有個(gè)用戶中心的組件,我們可以通過路由的方式去查詢用戶是否處于登陸狀態(tài),這種就不是狹義上的頁(yè)面路由的概念,通過一串字符串如何查到對(duì)應(yīng)的組件并調(diào)用其方法呢?這種的實(shí)現(xiàn)方式也有多種,每種實(shí)現(xiàn)方式都有自己的優(yōu)劣。


          一種是可以將服務(wù)抽象成接口,沉到底層,上層實(shí)現(xiàn)通過路由方式映射對(duì)象。


          一種是將實(shí)現(xiàn)方法直接通過路由方式映射。


          先看第一種,這種事Arouter的實(shí)現(xiàn)方式,它的優(yōu)點(diǎn)是所有對(duì)外暴露的服務(wù)都暴露接口類【沉到底層】,這對(duì)于外部的調(diào)用方,也就是服務(wù)使用方非常友好,示例如下:


          先定義抽象服務(wù),并沉到底層



          public?interface?HelloService?extends?IProvider?{
          ????void?sayHello(String?name);
          }


          實(shí)現(xiàn)服務(wù),并通過Router注解標(biāo)記。


          @Route(path?=?"/yourservicegroupname/hello")
          public?class?HelloServiceImpl?implements?HelloService?{
          ????Context?mContext;

          ????@Override
          ????public?void?sayHello(String?name)?{
          ????????Toast.makeText(mContext,?"Hello?"?+?name,?Toast.LENGTH_SHORT).show();
          ????}

          使用:利用Router加scheme獲取服務(wù)實(shí)例,并映射成抽象類,然后直接調(diào)用方法。


          ((HelloService)?ARouter.getInstance().build("/yourservicegroupname/hello").navigation()).sayHello("mike");


          這種實(shí)現(xiàn)方式對(duì)于使用方其實(shí)是很方便的,尤其是一個(gè)服務(wù)有多個(gè)可操作方法的時(shí)候,但是缺點(diǎn)是擴(kuò)展性,如果想要擴(kuò)展方法,就要改動(dòng)底層庫(kù)。


          再看第二種:將實(shí)現(xiàn)方法直接通過路由方式映射


          服務(wù)的調(diào)用都要落到方法上,參考頁(yè)面路由,也可以支持方法路由,兩者并列關(guān)系,所以主要增加一個(gè)方法路由表,實(shí)現(xiàn)原理與Page路由類似,跟上面的Arouter對(duì)比,不用定義抽象層,直接定義實(shí)現(xiàn)即可:


          定義Method的Router


          public?class?HelloService?{

          ????
          ????@MethodRouter(url?=?{"arouter://sayhello"})
          ????public?void?sayHello(String?name)?{
          ????????Toast.makeText(mContext,?"Hello?"?+?name,?Toast.LENGTH_SHORT).show();
          ????}

          使用即可


          RouterCall.callMethod("arouter://sayhello?name=hello");


          上述的缺點(diǎn)就是對(duì)于外部調(diào)用有些復(fù)雜,尤其是處理參數(shù)的時(shí)候,需要嚴(yán)格按照協(xié)議來處理,優(yōu)點(diǎn)是,沒有抽象層,如果需要擴(kuò)展服務(wù)方法,不需要改動(dòng)底層。


          上述兩種方式各有優(yōu)劣,不過,如果從做服務(wù)組件的初衷出發(fā),第一種比較好:對(duì)于調(diào)用方比較友好。另外對(duì)于CallBack的支持,Arouter的處理方式可能也會(huì)更方便一些,可以比較方便的交給服務(wù)方定義。如果是第二種,服務(wù)直接通過路由映射的方式,處理起來就比較麻煩,尤其是Callback中的參數(shù),可能要統(tǒng)一封裝成JSON并維護(hù)解析的協(xié)議,這樣處理起來,可能不是很好。


          路由表的匹配


          路由表的匹配比較簡(jiǎn)單,就是在全局Map中根據(jù)String輸入,匹配到目標(biāo)組件,然后依賴反射等常用操作,定位到目標(biāo)。

          6.組件化與路由的關(guān)系

          組件化是一種開發(fā)集成模式,更像一種開發(fā)規(guī)范,更多是為團(tuán)隊(duì)協(xié)同開發(fā)帶來方便。組件化最終落地是一個(gè)個(gè)獨(dú)立的業(yè)務(wù)及功能組件,這些組件之間可能是不同的團(tuán)隊(duì),處于不同的目的在各自維護(hù),甚至是需要代碼隔離,如果牽扯到組件間的調(diào)用與通信,就不可避免的借助路由,因?yàn)閷?shí)現(xiàn)隔離的,只能采用通用字符串scheme進(jìn)行通信,這就是路由的功能范疇。


          組件化需要路由支撐的根本原因:組件間代碼實(shí)現(xiàn)的隔離。


          總結(jié)


          路由不是一個(gè)APP的必備功能,但是大型跨團(tuán)隊(duì)的APP基本都需要。


          路由框架的基本能力:路由自動(dòng)注冊(cè)、路由表搜集、服務(wù)及UI界面路由及攔截等核心功能。


          組件化與路由的關(guān)系:組件化的代碼隔離導(dǎo)致路由框架成為必須。




          ? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!

          ? 『BATcoder』做了多年安卓還沒編譯過源碼?一個(gè)視頻帶你玩轉(zhuǎn)!

          ? 『BATcoder』我去!安裝Ubuntu還有坑?

          ? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!

          為了防止失聯(lián),歡迎關(guān)注我的小號(hào)

          ??微信改了推送機(jī)制,真愛請(qǐng)星標(biāo)本公號(hào)??
          瀏覽 42
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  操屄黄片 | 中日亚洲国产特级黄片 | 亚洲无码不卡视频在线观看 | 麻豆成人无码 | www.日韩乱码 |