<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [TOC] ![](https://img.kancloud.cn/22/37/223708ad080baf2d689411d07f629a51_1000x470.png) 最近在主導公司的 Android 客戶端架構演進工作,我們再次來認識下 MVC、MVP 與 MVVM。關于MV-X結構,我們在日常開發中目前應該是存在四種常見寫法,分別為:原始寫法、MVC模式寫法、MVP模式寫法、MVVM模式寫法。接下來我們一一來看。 ![](https://img.kancloud.cn/0f/ca/0fca5875e6642ecc9cd7676b7bef5199_1000x521.png) # 原始寫法 ## 概述 每個人在剛開始學習Android,第一個Activity中展示出HelloWorld時,用的就是原始寫法,我們一起來回顧下。 ![](https://img.kancloud.cn/14/80/1480ecae6a654239ad0b71779f207b10_674x468.png) ## demo及分析 一個Android原始寫法的示例如下: xml布局文件: ![](https://img.kancloud.cn/b3/e7/b3e77688bf44fc66592dd2b75570801b_1171x884.png) 布局文件很簡單,點擊‘獲取天氣’按鈕,根據輸入城市請求網絡獲取該城市天氣并展示。 Activity: ```java public class MainActivity extends AppCompatActivity { @BindView(R.id.et_city_id) EditText mEtCityId; @BindView(R.id.tv_weather_description) TextView mTvWeatherDescription; @BindView(R.id.tv_weather_temperature) TextView mTvWeatherTemperature; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); findViewById(R.id.tv_get_weather).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.weather.com.cn/data/sk/" + mEtCityId.getText().toString().trim()) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { listener.onError(e.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { Gson gson = new Gson(); Weather weather = gson.fromJson(response.body().string(), Weather.class); mTvWeatherDescription.setText(weather.getDescription()); mTvWeatherTemperature.setText(weather.getTemperature()); } }); } }); } } ``` ### demo分析 可以看到,View的事件響應、業務邏輯、View的更新全部寫在了Activity中,這就是項目中 Activity 動輒上千行的原因,代碼極度臃腫。在后期維護中,View 的變更、業務邏輯的變更、控制邏輯的變更,都需要直接對 Activity 進行修改,簡直就是一場噩夢。在軟件工程中講究低耦合高內聚,下面來看看如何一步步降低代碼的耦合度。 # MVC 模式 ## 概述 MVC 基于 Model-View-Controller,即模型-視圖-控制器的架構模式進行設計。 > * 模型對象:存儲著優雅的數據和業務邏輯,模型類通常用來映射與應用相關的一些事物,如用戶、商店里的商品、服務器上的圖片或者一段電視節目。模型對象不關心用戶界面,它存在的唯一目的就是存儲和管理應用數據。應用的全部模型對象組成了模型層。 >* 視圖對象:知道如何在屏幕上繪制自己以及如何響應用戶的輸入,如用戶的觸摸等。一個簡單的經驗法則是,凡是能夠在屏幕上看見的對象,就是視圖對象。 Android 默認自帶了很多可配置的視圖類。當然,也可以定制開發自己的視圖類。應用的全部視圖對象組成了視圖層。 >* 控制對象:含有應用的邏輯單元,是視圖與模型對象的聯系紐帶。控制對象響應視圖對象觸發的各類事件,此外還管理著模型對象與視圖間的數據流動。 在 Android 的世界里,控制器通常是 Activity、Fragment 或 Service 的一個子類。 ![](https://img.kancloud.cn/12/26/12266189d279c1f4ef662bb712fbd974_687x348.jpg) 上面這段話摘自《Android 編程權威指南》,反復細讀,會發現對于模型對象、視圖對象、控制對象各自的職責,描述特別的精準。為什么說精準呢,比如:對于視圖對象的描述是`凡是能夠在屏幕上看見的對象,就是視圖對象`,我閱讀后的理解是視圖對象就對應著控件,也就是視圖層是由各種控件組成,這不就是 xml 文件嘛,但是又不能說 xml 文件就是視圖層,這一點我們在后面會進行分析。 開始學習編程時,我當時對 Model、View、Controller 的理解是,Model 類似于 Java Bean,存儲數據時作為實體使用;Controller 為控制器,負責業務邏輯的處理。在2017年去杭州參加一次面試時,在面到 MVC 相關問題時,該公司 CTO 也對我進行了一些指點,觀點和上面書中的內容基本一致。當時沒有深入探究,今天再拿出來細細研讀,結合代碼,發現自己原來的理解一直都是錯誤的。 關鍵的一個錯誤點就是在于: * Model 并不是一個簡單的 Java Bean,而應該還負責業務邏輯處理、數據存儲讀取等 * Controller 并不是負責業務邏輯,而應該是負責應用的分發邏輯,是 View 和 Model 之間的橋梁,負責比如 View 上用戶的一個相關操作應該交給哪個 Model 去處理,一個 Model 處理完數據后該如何展現在 View 等 關于 MVC 各個模塊職責問題也可以結合知乎上[張恂老師的回答](https://www.zhihu.com/question/22886622/answer/48378638)來學習,很不錯的解答。 關于 MVC 還有一篇阮一峰老師的文章:[MVC,MVP 和 MVVM 的圖示](http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html),該篇文章和該文章的評論都很不錯。前端 MVC 和后端 MVC 的差別是什么呢,也許讀了該篇文章和評論后會有更多值得思考的。 ## 工作流程 MVC 模式的工作流程可以歸納為: * 視圖對象觸發各類事件,交由控制對象進行分發 * 控制對象選取合適的模型對象處理事件 * 模型對象處理完事件后,再由控制對象通知視圖對象進行更新 ![](https://img.kancloud.cn/a8/e2/a8e2c160a101b7dd8df6e4f8e2aa3092_664x388.png) ## demo及分析 附上一個使用 MVC 模式的 demo: Model 層如下: ```java public class WeatherModelImpl implements WeatherModel{ @Override public void getWeather(String cityId, final OnWeatherListener listener) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.weather.com.cn/data/sk/" + cityId) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { listener.onError(e.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { Gson gson = new Gson(); Weather weather = gson.fromJson(response.body().string(), Weather.class); listener.onSuccess(weather); } }); } } ``` View 層如下: ![](https://img.kancloud.cn/b3/e7/b3e77688bf44fc66592dd2b75570801b_1171x884.png) Controller 層如下: ```java public class WeatherActivity extends AppCompatActivity { @BindView(R.id.et_city_id) EditText mEtCityId; @BindView(R.id.tv_weather_description) TextView mTvWeatherDescription; @BindView(R.id.tv_weather_temperature) TextView mTvWeatherTemperature; private WeatherModel mWeatherModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_weather); ButterKnife.bind(this); mWeatherModel = new WeatherModelImpl(); } @OnClick(R.id.tv_get_weather) public void onViewClicked() { mWeatherModel.getWeather(mEtCityId.getText().toString().trim(), new OnWeatherListener() { @Override public void onSuccess(Weather weather) { mTvWeatherDescription.setText(weather.getDescription()); mTvWeatherTemperature.setText(weather.getTemperature()); } @Override public void onError(String msg) { Toast.makeText(WeatherActivity.this, msg, Toast.LENGTH_SHORT).show(); } }); } } ``` ### demo分析 ![](https://img.kancloud.cn/d0/2f/d02fbc86d47a9f338d6dec6505712d74_706x465.png) 在上面 demo 中,WeatherModelImpl 為 Model 對象,xml 布局為 View 對象,WeatherActivity 為 Controller 對象。 我們為獲取天氣信息這個動作(請求網絡數據),定義了一個接口 WeatherModel,該接口的實現類中進行網絡請求、回調 UI 更新操作,WeatherModel 就是前面所述的 Model 對象;WeatherActivity 作為 Controller 對象,僅僅負責在合適的時機調度相應的 Model 對象執行操作,在 demo 中就是在用戶點擊獲取天氣按鈕時,調用 WeatherModel 對象的方法去請求數據,并在數據獲取回調成功后,通知 View 層進行 UI 更新。 ## MVC與原始模式寫法對比 現在再回頭來看看MVC模式與原始寫法直接的區別: * 原始寫法中視圖對象對應著 xml 布局文件,而布局文件能做的事情很少,視圖繪制更新、事件處理實際是在 Activity 中完成的,所以 Activity 承擔了部分 View 層的工作;業務邏輯也大部分寫在了 Activity 中,所以 Activity 還承擔了大部分 Model 的工作;View 和 Model 的交互也是在 Activity 中,所以 Activity 還承擔了 Controller 的全部工作 * MVC 分層后,將業務邏輯從Activity中抽取出來,后期進行業務邏輯修改時,只需要修改對應的 Model 即可,降低了系統耦合度 【備注】 一種說法認為,Model層和View層也可以進行交互,即在創建Model對象時直接將View對象傳入,使得Model對象持有View引用,這樣需要更新視圖時直接更新View即可,我個人更傾向于使用接口的方式來更新View,以降低耦合。 ## MVC 與三層架構 關于 MVC 與三層架構的區別及聯系,我感覺自己現在依然無法很明白。此處引用一下網上的一種說法: > * 三層架構分層:表示層、業務層、持久層。表示層負責接收用戶請求、轉發請求、顯示數據等;業務層負責組織業務邏輯;持久層負責持久化業務對象 * 表示層最常用的架構模式就是MVC 個人理解: * 由于現在 Android 客戶端更多的數據是直接從服務端獲取并展示的,所以三層架構在 Android 客戶端的應用并不是很多 * 當然,三層架構也是可以應用于 Android 客戶端的 * 一個疑問:MVC 中 Model 層的數據持久化和三層架構中的持久層有什么區別與聯系呢 # MVP 模式 ## 概述 經過了 MVC 的分層后,代碼清晰了許多,但是還有問題:Model 層確實從 Activity 中分離出來了,而 View 層的部分代碼還在 Activity 中,也就是說現在 Activity 承擔著 View 層的部分功能和 Controller 的全部功能。下面該是 MVP 登場的時候了~ ![](https://img.kancloud.cn/93/7e/937e8bed9b73a1938b7e6b7f2e7ab2c5_692x478.png) 我們讓 Activity 來專一負責 View 層的功能(畢竟 xml 文件沒法增加額外的功能,那就交給老大哥 Activity 來做就好了)。原來 Activity 負責的 Controller 層全部功能,分離出來命名為 Presenter 層。Model 層不變。這樣 MVP 的層次劃分就出來了,來看個 demo: ## demo及分析 首先來看起到紐帶作用的 Presenter: ```java public class WeatherPresenter implements WeatherContract.Presenter { private WeatherContract.View mWeatherView; private WeatherModel mWeatherModel; public WeatherPresenter(WeatherContract.View weatherView) { mWeatherView = weatherView; mWeatherModel = new WeatherModel(); } @Override public void getWeather(String cityId) { mWeatherModel.getWeather(cityId, new OnWeatherListener() { @Override public void onSuccess(Weather weather) { mWeatherView.onUpdateWeatherSuccess(weather); } @Override public void onError(String msg) { mWeatherView.onUpdateWeatherFail(msg); } }); } } ``` Model 層代碼如下: ```java public class WeatherModel implements WeatherContract.Model { @Override public void getWeather(String cityId, final OnWeatherListener listener) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.weather.com.cn/data/sk/" + cityId) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { listener.onError(e.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { Gson gson = new Gson(); Weather weather = gson.fromJson(response.body().string(), Weather.class); listener.onSuccess(weather); } }); } } ``` View 層代碼如下: ```java public class WeatherActivity extends AppCompatActivity implements WeatherContract.View { @BindView(R.id.et_city_id) EditText mEtCityId; @BindView(R.id.tv_weather_description) TextView mTvWeatherDescription; @BindView(R.id.tv_weather_temperature) TextView mTvWeatherTemperature; private WeatherContract.Presenter mPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_weather); ButterKnife.bind(this); mPresenter = new WeatherPresenter(this); } @OnClick(R.id.tv_get_weather) public void onViewClicked() { mPresenter.getWeather(mEtCityId.getText().toString().trim()); } @Override public void onUpdateWeatherSuccess(Weather weather) { mTvWeatherDescription.setText(weather.getDescription()); mTvWeatherTemperature.setText(weather.getTemperature()); } @Override public void onUpdateWeatherFail(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } } ``` 其中接口定義代碼如下: ```java public interface WeatherContract { interface Model { void getWeather(String cityId, OnWeatherListener listener); } interface View { void onUpdateWeatherSuccess(Weather weather); void onUpdateWeatherFail(String msg); } interface Presenter { void getWeather(String cityId); } } ``` ### demo分析 通過 demo 可以看到,Presenter 作為 Model 和 View 之間的紐帶,會同時持有 Model 層對象和 View 層對象,這樣做的好處是,在需要的時候可以調用合適的 Model 對象去處理請求或數據,以及在數據處理完畢后調用 View 的方法去更新視圖。 Activity 作為 Android 四大組件之一,為應用的主要入口。在 MVP 模式中,Activity 作為 View 層對象,用戶操作 View 對象觸發的各類事件都交由 Presenter 層對象進行處理。所以讓 Activity 對象持有 Presenter 對象,可以在事件觸發時調用 Presenter 的接口來處理事件。同時在 Presenter 的構造器中將 Activity 自身傳遞過去,使得 Presenter 持有 Activity 的引用。 ## MVP 與 MVC 對比 ![](https://img.kancloud.cn/aa/90/aa90b02d28657e8005be7aaa33e8699a_459x440.png) 通過上面的流程圖可以看到,由 MVC 到 MVP 最根本的變化是: 1、MVC 中 Activity 所負責的控制器功能,單獨抽離出來并改名為 Presenter,Activity 專心負責視圖層。 2、MVC中Controller和View同處于Activity中,Controller是直接拿到View的UI節點,來更新UI;MVP中Presenter被單獨抽離,Presenter則是通過View提供的接口來更新UI,減少耦合。 ![](https://img.kancloud.cn/89/d1/89d1f2b97410abf6c0fecac8a2bd8c95_602x320.png) 事件流動方向不變。 # MVVM 模式 ## 概述 在介紹 MVVM 之前,我們先來回顧下 MVP 模式中的交互流程: * 用戶觸發 View 層事件時,View 對象會調用 Presenter 對象的方法把事件傳遞給 Presenter 進行處理,Presenter 會選擇合適的 Model 來處理事件 * 當 Model 處理完業務邏輯需要更新 UI 時,Model 會去通知 Presenter,Presenter 去獲取到 View 層的控件對象,然后調用控件的相應方法進行更新 UI。 MVVM 模式在 MVP 模式的基礎上進行了再次升級,把 View 與 Presenter 之間的交互做成全自動化了,不需要我們程序猿再去手動調用接口方法了,并且把 Presenter 再次改名為 ViewModel,先來看流程圖: ![](https://img.kancloud.cn/02/99/0299a46c82a731a91590ddd5cd0f7ae0_660x451.png) View 和 ViewModel 之間的自動化交互在 Android 平臺上有一個官方支持庫 DataBinding 來幫我們實現,我們只需要手動將 ViewModel 和 View 綁定到一起即可,用戶觸發 View 層事件會自動調用 ViewModel 層的方法,Model 處理完業務邏輯通知 ViewModel 后,ViewModel 對自身的狀態進行變更,此變更會自動把 View 層的 UI 進行更新。 ## demo及分析 View 層代碼: ```java public class WeatherActivity extends AppCompatActivity { private ActivityWeather2Binding mBinding; private WeatherViewModel mWeatherViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_weather2); mWeatherViewModel = new WeatherViewModel(new WeatherModel()); mBinding.setWeatherViewModel(mWeatherViewModel); } } ``` ```xml <?xml version="1.0" encoding="utf-8"?> <layout> <data> <variable name="weatherViewModel" type="com.xuchongyang.architecturedemo.mvvm.WeatherViewModel"/> </data> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" tools:context=".mvc.WeatherActivity"> <EditText android:id="@+id/et_city_id" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/tv_get_weather"/> <TextView android:id="@+id/tv_get_weather" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="獲取天氣" android:onClick="@{() -> weatherViewModel.getWeather()}" app:layout_constraintStart_toEndOf="@id/et_city_id" app:layout_constraintTop_toTopOf="@id/et_city_id" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/et_city_id"/> <TextView android:id="@+id/tv_weather_description" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" tools:text="天氣:" android:text="@{weatherViewModel.mDescription}" app:layout_constraintTop_toBottomOf="@id/et_city_id"/> <TextView android:id="@+id/tv_weather_temperature" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" tools:text="氣溫:" android:text="@{weatherViewModel.mTemperature}" app:layout_constraintTop_toBottomOf="@id/tv_weather_description"/> </android.support.constraint.ConstraintLayout> </layout> ``` ViewModel 層代碼: ```java public class WeatherViewModel { public final ObservableField<String> mDescription = new ObservableField<>(); public final ObservableField<String> mTemperature = new ObservableField<>(); private WeatherModel mWeatherModel; public WeatherViewModel(WeatherModel weatherModel) { mWeatherModel = weatherModel; } public void getWeather() { mWeatherModel.getWeather("", new OnWeatherListener() { @Override public void onSuccess(Weather weather) { mDescription.set(weather.getDescription()); mTemperature.set(weather.getTemperature()); } @Override public void onError(String msg) { } }); } } ``` Model 層代碼: ```java public class WeatherModel implements IWeatherModel { @Override public void getWeather(String cityId, final OnWeatherListener listener) { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.weather.com.cn/data/sk/" + cityId) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { listener.onError(e.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { Gson gson = new Gson(); Weather weather = gson.fromJson(response.body().string(), Weather.class); listener.onSuccess(weather); } }); } } ``` ### demo分析 ```java mBinding = DataBindingUtil.setContentView(this, R.layout.activity_weather2); mWeatherViewModel = new WeatherViewModel(new WeatherModel()); mBinding.setWeatherViewModel(mWeatherViewModel); ``` 從上面代碼中可以看到 View 和 ViewModel 的綁定過程,首先獲取到 DataBinding 幫我們生成的 View 對象對應的 DataBinding 對象,然后為其設置 ViewModel,這樣就將二者綁定到一起了。 ```xml <TextView android:id="@+id/tv_get_weather" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="獲取天氣" android:onClick="@{() -> weatherViewModel.getWeather()}" app:layout_constraintStart_toEndOf="@id/et_city_id" app:layout_constraintTop_toTopOf="@id/et_city_id" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/et_city_id"/> ``` 上面代碼可以看到,用戶觸發的 View 層事件,可以直接在 View 層寫代碼,如上面示例所示,用戶點擊時,DataBinding 庫會自動調用 ViewModel 的 getWeather 方法。 ```java mWeatherModel.getWeather("", new OnWeatherListener() { @Override public void onSuccess(Weather weather) { mDescription.set(weather.getDescription()); mTemperature.set(weather.getTemperature()); } @Override public void onError(String msg) { } }); ``` 上面代碼可以看到,當 Model 處理完業務邏輯,需要更新 UI 時,直接更改 ViewModel 層的屬性狀態即可,View 層 UI 會自動進行更新。 ## MVVM 與 MVP 對比 綜上可以看到,MVVM 相比于 MVP 升級的地方在于:View 調用 Presenter 接口的操作和 Presenter 獲取 View 控件并更新 UI 的操作,全部自動化了,變為雙向動態綁定。Presenter 和 Model 的交互過程不變。 # 總結 至此,我們已經分析完了 Android 開發中三種常見的架構模式,最普通的編碼方法將視圖、業務邏輯、應用邏輯全部放在 Activity 中,造成 Activity 異常臃腫,維護困難;那我們就把 Activity 中業務邏輯部分的代碼抽取出來,作為 Model 層,使業務和視圖進行分離,代碼耦合性得到有效降低,這就是 MVC 模式;然而雖然業務邏輯代碼被抽出來了,Activity 依然負責著視圖層代碼和應用控制邏輯代碼,解耦依舊不徹底,那我們就把應用控制邏輯代碼也抽出來,讓 Activity 專心負責視圖代碼,這就是 MVP 模式;由于視圖代碼和控制邏輯代碼分離了,在更新視圖時就要考慮到 Activity 的生命周期問題,并且視圖和 Presenter 間的接口粒度也不好控制,那我們就把視圖和 Presenter 之間的交互做成全自動的吧,我們就不需要手動調用接口了,這就是 MVVM 模式。 可以看到,每一種模式都是在前一種模式的基礎上進行一定的優化,解決了一個個痛點。但任何事物都是兩面性的,升級到最后的 MVVM 模式后,并不是就完美了,由于代碼的封裝,代碼的可讀性變差,并且造成在調試時就比較困難。當然,模式是死的,項目和程序員是活的,我們可以選擇適合自己適合項目的模式去編碼,保持高內聚低耦合,遵從良好的編碼規范。 # 參考資料 [如何理解 MVC 中的 Model?](https://www.zhihu.com/question/22886622/answer/48378638) [三層構架和 MVC 不同嗎?](https://www.zhihu.com/question/24291079/answer/27339010) [MVVM 在 Android 上的正確使用方式](https://tangpj.com/2016/12/04/mvvm-structure-introduction/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看