<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>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                這一節將分析AccountManagerService中的一個重要的函數,即addAccount,其功能是為某項賬戶添加一個用戶。下面以前面提及的Email為例來認識AAS的處理流程。 AccountManagerService是一個運行在SystemServer中的服務,客戶端進程必須借助AccountManager提供的API來使用AccountManagerService服務,所以,本例需從AccountManager的addAccount函數講起。 1. AccountManager的addAccount發起請求 AccountManager 的addAccount函數的參數和返回值較復雜,先看其函數原型: ~~~ public AccountManagerFuture<Bundle>addAccount( finalString accountType, finalString authTokenType, finalString[] requiredFeatures, finalBundle addAccountOptions, finalActivity activity, AccountManagerCallback<Bundle>callback, Handlerhandler) ~~~ 在以上代碼中: - addAccount的返回值類型是AccountManagerFuture<Bundle>。其中,AccountManagerFuture是一個模板Interface,其真實類型只有在分析addAccount的實現時才能知道。現在可以告訴讀者的是,它和Java并發庫(concurrent庫)中的FutureTask有關,是對異步函數調用的一種封裝[^①]。調用者在后期只要調用它的getResult函數即可取得addAccount的調用結果。由于addAccount可能涉及網絡操作(例如,AAS需要把賬戶添加到網絡服務器上),所以這里采用了異步調用的方法以避免長時間的阻塞。這也是AccountManagerFuture的getResult不能在主線程中調用的原因。 - addAccount的第一個參數accountType代表賬戶類型。該參數不能為空。就本例而言,它的值為“com.android.email”。 - authTokenType、requiredFeatures和addAccountOptions與具體的AAS服務有關。如果想添加指定賬戶類型的Account,則須對其背后的AAS有所了解。 - activity:此參數和界面有關。例如有些AAS需要用戶輸入用戶名和密碼,故需啟動一Activity。在這種情況下,AAS會返回一個Intent,客戶端將通過這個activity啟動Intent所標示的Activity。讀者將通過下文的分析了解這一點。 - callback和handler:這兩個參數與如何獲取addAccount返回結果有關。如這兩個參數為空,客戶端則須單獨啟動一個線程去調用AccountManagerFuture的getResult函數。 addAccount的代碼如下: **AccountManager.java::addAccount** ~~~ public AccountManagerFuture<Bundle>addAccount(final String accountType, finalString authTokenType, final String[] requiredFeatures, finalBundle addAccountOptions, final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { if(accountType == null) //accountType不能為null thrownew IllegalArgumentException("accountType is null"); finalBundle optionsIn = new Bundle(); if(addAccountOptions != null)//保存客戶端傳入的addAccountOptions optionsIn.putAll(addAccountOptions); optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); //構造一個匿名類對象,該類繼承自AmsTask,并實現了doWork函數。addAccount返回前 //將調用該對象的start函數 returnnew AmsTask(activity, handler, callback) { publicvoid doWork() throws RemoteException { //mService用于和AccountManagerService通信 mService.addAcount(mResponse, accountType, authTokenType, requiredFeatures, activity!= null, optionsIn); } }.start(); } ~~~ 在以上代碼中,AccountManager的 addAccount函數將返回一個匿名類對象,該匿名類繼承自AmsTask類。那么,AmsTask又是什么呢? (1) AmsTask介紹 先來看AmsTask的繼承關系,如圖8-7所示。 :-: ![](http://img.blog.csdn.net/20150803131511362?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖8-7 AmsTask繼承關系 由圖8-7可知: - AmsTask繼承自FutureTask,并實現了AccountManagerFuture接口。FutureTask是Java concurrent庫中一個常用的類。AmsTask定義了一個doWork虛函數,該函數必須由子類來實現。 - 一個AmsTask對象中有一個mResponse成員,該成員的類型是AmsTask中的內部類Response。從Response的派生關系可知,Response將參與Binder通信,并且它是Binder通信的Bn端。而AccountManagerService的addAccount將得到它的Bp端對象。當添加完賬戶后,AccountManagerService會通過這個Bp端對象的onResult或onError函數向Response通知處理結果。 (2) AmsTask匿名類處理分析 AccountManager的addAccount最終返回給客戶端的是一個AmsTask的子類,首先來了解它的構造函數,其代碼如下: **AccountManager.java::AmsTask** ~~~ public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback){ ......//調用基類構造函數 //保存客戶端傳遞的參數 mHandler= handler; mCallback = callback; mActivity = activity; mResponse = new Response();//構造一個Response對象,并保存到mResponse中 } ~~~ 下一步調用的是這個匿名類的start函數,代碼如下: **AccountManager.java::AmsTask.start** ~~~ public final AccountManagerFuture<Bundle>start() { try { doWork(); //調用匿名類實現的doWork函數 }...... returnthis; } ~~~ 匿名類實現的doWork函數即下面這個函數: **AccountManager.java::addAccount返回的匿名類** ~~~ public void doWork() throws RemoteException { //調用AccountManagerService的addAccount函數,其第一個參數是mResponse mService.addAcount(mResponse, accountType, authTokenType, requiredFeatures, activity != null, optionsIn); } ~~~ AccountManager的addAccount函數的實現比較新奇,它內部使用了Java的concurrent類。不熟悉Java并發編程的讀者有必要了解相關知識。 下面轉到AccountManagerService中去分析addAccount的實現。 2. AccountManagerService addAccount轉發請求 AccountManagerServiceaddAccount的代碼如下所示: **AccountManagerService.java::addAccount** ~~~ public void addAcount(finalIAccountManagerResponse response, finalString accountType, final String authTokenType, final String[] requiredFeatures, final boolean expectActivityLaunch, final Bundle optionsIn) { ...... //檢查客戶端進程是否有“android.permission.MANAGE_ACCOUNTS”的權限 checkManageAccountsPermission(); final intpid = Binder.getCallingPid(); final intuid = Binder.getCallingUid(); //構造一個Bundle類型的options變量,并保存傳入的optionsIn finalBundle options = (optionsIn == null) ? new Bundle() : optionsIn; options.putInt(AccountManager.KEY_CALLER_UID, uid); options.putInt(AccountManager.KEY_CALLER_PID, pid); longidentityToken = clearCallingIdentity(); try { //創建一個匿名類對象,該匿名類派生自Session類。最后調用該匿名類的bind函數 new Session(response, accountType, expectActivityLaunch,true){ public void run() throws RemoteException { mAuthenticator.addAccount(this, mAccountType, authTokenType,requiredFeatures, options); } protected String toDebugString(longnow) { ......//實現toDebugString函數 } }.bind(); }finally { restoreCallingIdentity(identityToken); } } ~~~ 由以上代碼可知,AccountManagerService的addAccount函數最后也創建了一個匿名類對象,該匿名類派生自Session。addAccount最后還調用了這個對象的bind函數。其中最重要的內容就是Session。那么,Session又是什么呢? (1) Session介紹 Session家族成員如圖8-8所示。 :-: ![](http://img.blog.csdn.net/20150803131528312?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖8-8 Session家族示意圖 由圖8-8可知: - Session從IAccountAuthenticatorResponse.Stub派生,這表明它將參與Binder通信,并且它是Bn端。那么這個Binder通信的目標是誰呢?,它正是具體的AAS服務。AccountManagerService會將自己傳遞給AAS,這樣,AAS就得到IAccountAuthenticatorResponse的Bp端對象。當AAS完成了具體的賬戶添加工作后,會通過IAccountAuthenticatorResponse的Bp端對象向Seession返回處理結果。 - Session通過mResponse成員變量指向來自客戶端的IAccountManagerResponse接口,當Session收到AAS的返回結果后,又通過IAccountManagerResponse 的Bp端對象向客戶端返回處理結果。 - Session mAuthenticator變量的類型是IAccountAuthenticator,它用于和遠端的AAS通信。客戶端發起的請求將通過Session經由mAuthenticator調用對應AAS中的函數。 由圖8-7和圖8-8可知,AccountManagerService在addAccount流程中所起的是橋梁作用,具體如下: - 客戶端將請求發送給AccountManagerService,然后AccountManagerService再轉發給對應的AAS。 - AAS處理完的結果先返回給AccountManagerService,再由AccountManagerService返回給客戶端。 由于圖8-7和圖8-8中定義的類名較相似,因此讀者閱讀時應仔細一些。 下面來看Session匿名類的處理。 (2) Session匿名類處理分析 首先調用Session的構造函數,代碼為: **AccountManagerService.java::Session** ~~~ public Session(IAccountManagerResponse response,String accountType, boolean expectActivityLaunch, boolean stripAuthTokenFromResult) { super(); ...... /* 注意其中的參數,expectActivityLaunch由客戶端傳來,如果用戶調用 AccountManager addAccount時傳入了activity參數,則該值為true, stripAuthTokenFromResult的默認值為true */ mStripAuthTokenFromResult = stripAuthTokenFromResult; mResponse = response; mAccountType = accountType; mExpectActivityLaunch = expectActivityLaunch; mCreationTime = SystemClock.elapsedRealtime(); synchronized(mSessions) { //將這個匿名類對象保存到AccountManagerService中的mSessions成員中 mSessions.put(toString(), this); } try{ //監聽客戶端死亡消息 response.asBinder().linkToDeath(this, 0); } ...... } ~~~ 獲得匿名類對象后,addAccount將調用其bind函數,該函數由AmsTask實現,代碼如下: **AccountManagerService.java::Session:bind** ~~~ void bind() { //綁定到mAccountType指定的AAS。在本例中,AAS的類型是“com.android.email” if(!bindToAuthenticator(mAccountType)) { onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bindfailure"); } } ~~~ bindToAuthenticator的代碼為: **AccountManagerService.java::Session:bindToAuthenticator** ~~~ private boolean bindToAuthenticator(StringauthenticatorType) { //從mAuthenticatorCache中查詢滿足指定類型的服務信息 AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo = mAuthenticatorCache.getServiceInfo( AuthenticatorDescription.newKey(authenticatorType)); ...... Intentintent = new Intent(); intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT); //設置目標服務的ComponentName intent.setComponent(authenticatorInfo.componentName); //通過bindService啟動指定的服務,成功與否將通過第二個參數傳遞的 //ServiceConnection接口返回 if(!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) { ...... } returntrue; } ~~~ 由以上代碼可知,Session的bind函數將啟動指定類型的Service,這是通過bindService函數完成的。如果服務啟動成功,Session的onServiceConnected函數將被調用,這部分代碼如下: **AccountManagerService.java::Session:onServiceConnected** ~~~ public void onServiceConnected(ComponentName name,IBinder service) { //得到遠端AAS返回的IAccountAuthenticator接口,這個接口用于 //AccountManagerService和該遠端AAS交互 mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); try { run();//調用匿名類實現的run函數 } ...... } ~~~ 匿名類實現的run函數非常簡單,代碼如下: **AccountManagerService.java::addAccount返回的匿名類** ~~~ new Session(response, accountType,expectActivityLaunch,true) { publicvoid run() throws RemoteException { //調用遠端AAS實現的addAccount函數 mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,options); } ~~~ 由以上代碼可知,AccountManagerService在addAccount最終將調用AAS實現的addAccount函數。 下面來看本例中滿足“com.android.email”類型的服務是如何處理addAccount的請求的。該服務就是Email應用中的EasAuthenticatorService,下面來分析它。 3. EasAuthenticatorService處理請求 EasAuthenticatorService的創建是AccountManagerService調用了bindService導致的,該函數會觸發EasAuthenticatorService的onBind函數被調用,這部分代碼如下: **EasAuthenticatorService.java::onBind** ~~~ public IBinder onBind(Intent intent) { if(AccountManager.ACTION_AUTHENTICATOR_INTENT.equals( intent.getAction())){ //創建一個EasAuthenticator類型的對象,并調用其getIBinder函數 return new EasAuthenticator(this).getIBinder(); }else return null; } ~~~ 下面來分析EasAuthenticator。 (1) EasAuthenticator介紹 EasAuthenticator是EasAuthenticatorService定義的內部類,其家族關系如圖8-9所示。 :-: ![](http://img.blog.csdn.net/20150803131545395?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖8-9 EasAuthenticator家族類圖 由圖8-9可知: - EasAuthenticator從AbstractAccountAuthenticator類派生。AbstractAccountAuthenticator內部有一個mTransport的成員變量,其類型是AbstractAccountAuthenticator的內部類Transport。在前面的onBind函數中,EasAuthenticator的getIBinder函數返回的就是這個變量。 - Transport類繼承自Binder,故它將參與Binder通信,并且是IAccountAuthenticator的Bn端。Session匿名類通過onServiceConnected函數將得到一個IAccountAuthenticator的Bp端對象。 當由AccoutManagerService的addAccount創建的那個Session匿名類調用IAccountAuthenticator Bp端對象的addAccount時,將觸發位于Emai進程中的IAccountAuthenticatorBn端的addAccount。下面來分析Bn端的addAccount函數。 (2) EasAuthenticator的 addAccount函數分析 根據上文的描述可知,Emai 進程中首先被觸發的是IAccountAuthenticatorBn端的addAccount函數,其代碼如下: **AbstractAccountAuthenticator.java::Transport:addAccount** ~~~ private class Transport extendsIAccountAuthenticator.Stub { publicvoid addAccount(IAccountAuthenticatorResponse response, String accountType, String authTokenType, String[] features, Bundle options) throws RemoteException { //檢查權限 checkBinderPermission(); try { //調用AbstractAccountAuthenticator子類實現的addAccount函數 final Bundle result = AbstractAccountAuthenticator.this.addAccount( new AccountAuthenticatorResponse(response), accountType,authTokenType, features, options); //如果返回的result不為空,則調用response的onResult返回結果。 //這個response是IAccountAuthenticatorResponse類型,它的onResult //將觸發位于Session匿名類中mResponse變量的onResult函數被調用 if (result != null) response.onResult(result); }...... } ~~~ 本例中AbstractAccountAuthenticator子類(即EasAuthenticator)實現的addAccount函數,代碼如下: **EasAuthenticatorService.java::EasAuthenticator.addAccount** ~~~ public BundleaddAccount(AccountAuthenticatorResponse response, String accountType, StringauthTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { //EasAuthenticatoraddAccount的處理邏輯和Email應用有關。只做簡單了解即可 //如果用戶傳遞的賬戶信息保護了密碼和用戶名,則走if分支。注意,其中有一些參數名是 //通用的,例如OPTIONS_PASSWORD,OPTIONS_USERNAME等 if(options != null && options.containsKey(OPTIONS_PASSWORD) && options.containsKey(OPTIONS_USERNAME)) { //創建一個Account對象,該對象僅包括兩個成員,一個是name,用于表示賬戶名; //另一個是type,用于表示賬戶類型 finalAccount account = new Account(options.getString(OPTIONS_USERNAME), AccountManagerTypes.TYPE_EXCHANGE); //調用AccountManager的addAccountExplicitly將account對象和password傳遞 //給AccountManagerService處理。讀者可自行研究這個函數,在其內部將這些信息寫入 //accounts.db的account表中 AccountManager.get(EasAuthenticatorService.this). addAccountExplicitly(account, options.getString(OPTIONS_PASSWORD), null); //根據Email應用的規則,下面將判斷和該賬戶相關的數據是否需要設置自動數據同步 //首先判斷是否需要處理聯系人自動數據同步 booleansyncContacts = false; if(options.containsKey(OPTIONS_CONTACTS_SYNC_ENABLED) && options.getBoolean(OPTIONS_CONTACTS_SYNC_ENABLED)) syncContacts = true; ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY,1); ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY,syncContacts); booleansyncCalendar = false; ......//判斷是否需要設置Calendar自動數據同步 booleansyncEmail = false; //如果選項中包含Email同步相關的功能,則需要設置Email數據同步的相關參數 if(options.containsKey(OPTIONS_EMAIL_SYNC_ENABLED) && options.getBoolean(OPTIONS_EMAIL_SYNC_ENABLED)) syncEmail = true; /* 下面這兩個函數將和ContentService中的SyncManager交互。注意這 兩個函數:第一個函數setIsSyncable將設置Email對應的同步服務功能標志, 第二個函數setSyncAutomatically將設置是否自動同步Email。 數據同步的內容留待8.4節再詳細分析 */ ContentResolver.setIsSyncable(account,EmailContent.AUTHORITY, 1); ContentResolver.setSyncAutomatically(account,EmailContent.AUTHORITY, syncEmail); //構造返回結果,注意,下面這些參數名是Android統一定義的,如KEY_ACCOUNT_NAME等 Bundleb = new Bundle(); b.putString(AccountManager.KEY_ACCOUNT_NAME, options.getString(OPTIONS_USERNAME)); b.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTypes.TYPE_EXCHANGE); returnb; } else { //如果沒有傳遞password,則需要啟動一個Activity,該Activity對應的Intent //由actionSetupExchangeIntent返回。注意下面幾個通用的參數,如 // KEY_ACCOUNT_AUTHENTICATOR_RESPONSE和KEY_INTENT Bundle b = new Bundle(); Intent intent =AccountSetupBasics.actionSetupExchangeIntent( EasAuthenticatorService.this); intent.putExtra( AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); b.putParcelable(AccountManager.KEY_INTENT,intent); return b; } } ~~~ 不同的AAS有自己特定的處理邏輯,以上代碼向讀者展示了EasAuthenticatorService的工作流程。雖然每個AAS的處理方式各有不同,但Android還是定義了一些通用的參數,例如OPTIONS_USERNAME用于表示用戶名,OPTIONS_PASSWORD用于表示密碼等。關于這方面內容,讀者可查閱SDK中AccountManager的文檔說明。 4. 返回值的處理流程 在EasAuthenticator的addAccount返回處理結果后,AbstractAuthenticator將通過IAccountAuthenticatorResponse的onResult將其返回給由AccountManagerService創建的Session匿名類對象。來看Session的onResult函數,其代碼如下: **AccountManagerService.java::Session:onResult** ~~~ public void onResult(Bundle result) { mNumResults++; //從返回的result中取出相關信息,關于下面這個if分支處理和狀態欄中相關的邏輯, //讀者可自行研究 if (result != null&&!TextUtils.isEmpty( result.getString(AccountManager.KEY_AUTHTOKEN))) { String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); if(!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)){ Account account = new Account(accountName, accountType); cancelNotification( getSigninRequiredNotificationId(account)); } } IAccountManagerResponse response; //如果客戶端傳遞了activity參數,則mExpectActivityLaunch為true。如果 //AAS返回的結果中包含KEY_INTENT,則表明需要彈出Activity以輸入賬戶和密碼 if(mExpectActivityLaunch && result != null && result.containsKey(AccountManager.KEY_INTENT)) { response = mResponse; } else { /* getResponseAndClose返回的也是mResponse,不過它會調用unBindService 斷開和AAS服務的連接。就整個流程而言,此時addAccount已經完成AAS和 AccountManagerService的工作,故無需再保留和AAS服務的連接。而由于上面的if 分支還需要輸入用戶密碼,因此以AAS和AccountManagerService之間的工作 還沒有真正完成 */ response = getResponseAndClose(); } if(response != null) { try{ ...... if (mStripAuthTokenFromResult) result.remove(AccountManager.KEY_AUTHTOKEN); //調用位于客戶端的IAccountManagerResponse的onResult函數 response.onResult(result); } ...... } } ~~~ 客戶端的IAccountManagerResponse接口由AmsTask內部類Response實現,其代碼為: **AccountManager.java::AmsTask::Response.onResult** ~~~ public void onResult(Bundle bundle) { Intentintent = bundle.getParcelable(KEY_INTENT); //如果需要彈出Activity,則要利用客戶端傳入的那個activity去啟動AAS指定的 //Activity。這個Activity由AAS返回的Intent來表示 if(intent != null && mActivity != null) { mActivity.startActivity(intent); } elseif (bundle.getBoolean("retry")) { //如果需要重試,則再次調用doWork try { doWork(); }...... }else{ //將返回結果保存起來,當客戶端調用getResult時,就會得到相關結果 set(bundle); } } ~~~ 5. AccountManager的addAccount分析總結 AccountManager的addAccount流程分析起來給人一種行云流水般的感覺。該流程涉及3個模塊,分別是客戶端、AccountManagerService和AAS。整體難度雖不算大,但架構卻比較巧妙。 總結起來addAccount相關流程如圖8-10所示。 :-: ![](http://img.blog.csdn.net/20150803131604163?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖8-10 AccountManager的addAccount處理流程 為了讓讀者看得更清楚,圖8-10中略去了一些細枝末節的內容。另外,圖8-10中第10步的onServiceConnected函數應由位于SystemServer中的ActivityThread對象調用,為方便閱讀起見,這里沒有畫出ActivityThread的對象。 [^①]:從設計模式角度來說,這屬于ActiveObject模式。詳細內容可閱讀《Pattern.Oriented.Software.Architecture.Volume.2》的第2章“Concurrency Patterns”。
                  <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>

                              哎呀哎呀视频在线观看