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

          HarmonyOS學(xué)習(xí)路之開(kāi)發(fā)篇—Data Ability

          共 15349字,需瀏覽 31分鐘

           ·

          2021-07-01 22:04

          點(diǎn)擊上方藍(lán)色字體,關(guān)注我們



          1


          Data Ability基本概念


          使用Data模板的Ability(以下簡(jiǎn)稱“Data”)有助于應(yīng)用管理其自身和其他應(yīng)用存儲(chǔ)數(shù)據(jù)的訪問(wèn),并提供與其他應(yīng)用共享數(shù)據(jù)的方法。Data既可用于同設(shè)備不同應(yīng)用的數(shù)據(jù)共享,也支持跨設(shè)備不同應(yīng)用的數(shù)據(jù)共享。


          數(shù)據(jù)的存放形式多樣,可以是數(shù)據(jù)庫(kù),也可以是磁盤上的文件。Data對(duì)外提供對(duì)數(shù)據(jù)的增、刪、改、查,以及打開(kāi)文件等接口,這些接口的具體實(shí)現(xiàn)由開(kāi)發(fā)者提供。


          URI介紹


          Data的提供方和使用方都通過(guò)URI(Uniform Resource Identifier)來(lái)標(biāo)識(shí)一個(gè)具體的數(shù)據(jù),例如數(shù)據(jù)庫(kù)中的某個(gè)表或磁盤上的某個(gè)文件。HarmonyOS的URI仍基于URI通用標(biāo)準(zhǔn),格式如下:


          • scheme:協(xié)議方案名,固定為“dataability”,代表Data Ability所使用的協(xié)議類型
          • authority:設(shè)備ID。如果為跨設(shè)備場(chǎng)景,則為目標(biāo)設(shè)備的ID;如果為本地設(shè)備場(chǎng)景,則不需要填寫。
          • path:資源的路徑信息,代表特定資源的位置信息。
          • query:查詢參數(shù)。f
          • ragment:可以用于指示要訪問(wèn)的子資源。

          URI示例:


          • 跨設(shè)備場(chǎng)景:dataability://device_id/com.domainname.dataability.persondata/person/10
          • 本地設(shè)備:dataability:///com.domainname.dataability.persondata/person/10


          2


          創(chuàng)建Data


          使用Data模板的Ability形式仍然是Ability,因此,開(kāi)發(fā)者需要為應(yīng)用添加一個(gè)或多個(gè)Ability的子類,來(lái)提供程序與其他應(yīng)用之間的接口。Data為結(jié)構(gòu)化數(shù)據(jù)和文件提供了不同API接口供用戶使用,因此,開(kāi)發(fā)者需要首先確定好使用何種類型的數(shù)據(jù)。


          本章節(jié)主要講述了創(chuàng)建Data的基本步驟和需要使用的接口。Data提供方可以自定義數(shù)據(jù)的增、刪、改、查,以及文件打開(kāi)等功能,并對(duì)外提供這些接口。


          確定數(shù)據(jù)存儲(chǔ)方式


          確定數(shù)據(jù)的存儲(chǔ)方式,Data支持以下兩種數(shù)據(jù)形式:

          • 文件數(shù)據(jù):如文本、圖片、音樂(lè)等。
          • 結(jié)構(gòu)化數(shù)據(jù):如數(shù)據(jù)庫(kù)等。

          實(shí)現(xiàn)UserDataAbility


          UserDataAbility用于接收其他應(yīng)用發(fā)送的請(qǐng)求,提供外部程序訪問(wèn)的入口,從而實(shí)現(xiàn)應(yīng)用間的數(shù)據(jù)訪問(wèn)。


          實(shí)現(xiàn)UserDataAbility,需要在“Project”窗口當(dāng)前工程的主目錄(“entry > src > main > java > com.xxx.xxx”)選擇“File > New > Ability > Empty Data Ability”,設(shè)置“Data Name”后完成UserDataAbility的創(chuàng)建。


          Data提供了文件存儲(chǔ)和數(shù)據(jù)庫(kù)存儲(chǔ)兩組接口供用戶使用。


          文件存儲(chǔ)


          開(kāi)發(fā)者需要在Data中重寫FileDescriptor openFile(Uri uri, String mode)方法來(lái)操作文件:uri為客戶端傳入的請(qǐng)求目標(biāo)路徑;mode為開(kāi)發(fā)者對(duì)文件的操作選項(xiàng),可選方式包含“r”(讀), “w”(寫), “rw”(讀寫)等。


          ohos.rpc.MessageParcel類提供了一個(gè)靜態(tài)方法,用于獲取MessageParcel實(shí)例。開(kāi)發(fā)者可通過(guò)獲取到的MessageParcel實(shí)例,使用dupFileDescriptor()函數(shù)復(fù)制待操作文件流的文件描述符,并將其返回,供遠(yuǎn)端應(yīng)用訪問(wèn)文件。


          示例:根據(jù)傳入的uri打開(kāi)對(duì)應(yīng)的文件。


          private static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0xD00201"Data_Log");

          @Override
          public FileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
              // 創(chuàng)建messageParcel
              MessageParcel messageParcel = MessageParcel.obtain();
              File file = new File(uri.getDecodedPathList().get(0)); //get(0)是獲取URI完整字段中查詢參數(shù)字段。
              if (mode == null || !"rw".equals(mode)) {
                  file.setReadOnly();
              }
              FileInputStream fileIs = new FileInputStream(file);
              FileDescriptor fd = null;
              try {
                  fd = fileIs.getFD();
              } catch (IOException e) {
                  HiLog.info(LABEL_LOG, "failed to getFD");
              }

              // 綁定文件描述符
              return messageParcel.dupFileDescriptor(fd);
          }


          數(shù)據(jù)庫(kù)存儲(chǔ)


          1、初始化數(shù)據(jù)庫(kù)連接。系統(tǒng)會(huì)在應(yīng)用啟動(dòng)時(shí)調(diào)用onStart()方法創(chuàng)建Data實(shí)例。在此方法中,開(kāi)發(fā)者應(yīng)該創(chuàng)建數(shù)據(jù)庫(kù)連接,并獲取連接對(duì)象,以便后續(xù)和數(shù)據(jù)庫(kù)進(jìn)行操作。為了避免影響應(yīng)用啟動(dòng)速度,開(kāi)發(fā)者應(yīng)當(dāng)盡可能將非必要的耗時(shí)任務(wù)推遲到使用時(shí)執(zhí)行,而不是在此方法中執(zhí)行所有初始化。


          示例:初始化的時(shí)候連接數(shù)據(jù)庫(kù)。


          private static final String DATABASE_NAME = "UserDataAbility.db";
          private static final String DATABASE_NAME_ALIAS = "UserDataAbility";
          private static final HiLogLabel LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0xD00201"Data_Log");
          private OrmContext ormContext = null;

          @Override
          public void onStart(Intent intent) {
              super.onStart(intent);
              DatabaseHelper manager = new DatabaseHelper(this);
              ormContext = manager.getOrmContext(DATABASE_NAME_ALIAS, DATABASE_NAME, BookStore.class);
          }


          2、編寫數(shù)據(jù)庫(kù)操作方法。Ability定義了6個(gè)方法供用戶處理對(duì)數(shù)據(jù)庫(kù)表數(shù)據(jù)的增刪改查。這6個(gè)方法在Ability中已默認(rèn)實(shí)現(xiàn),開(kāi)發(fā)者可按需重寫。


          方法描述
          ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates)查詢數(shù)據(jù)庫(kù)
          int insert(Uri uri, ValuesBucket value)向數(shù)據(jù)庫(kù)中插入單條數(shù)據(jù)
          int batchInsert(Uri uri, ValuesBucket[] values)向數(shù)據(jù)庫(kù)中插入多條數(shù)據(jù)
          int delete(Uri uri, DataAbilityPredicates predicates)刪除一條或多條數(shù)據(jù)
          int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates)更新數(shù)據(jù)庫(kù)
          DataAbilityResult[] executeBatch(ArrayListoperations)批量操作數(shù)據(jù)庫(kù)


          3、批量操作數(shù)據(jù)庫(kù) 這些方法的使用說(shuō)明如下:


          • query() 該方法接收三個(gè)參數(shù),分別是查詢的目標(biāo)路徑,查詢的列名,以及查詢條件,查詢條件由類DataAbilityPredicates構(gòu)建。根據(jù)傳入的列名和查詢條件查詢用戶表的代碼示例如下:
          public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
              if (ormContext == null) {
                  HiLog.error(LABEL_LOG, "failed to query, ormContext is null");
                  return null;
              }

              // 查詢數(shù)據(jù)庫(kù)
              OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates,User.class);
              ResultSet resultSet = ormContext.query(ormPredicates, columns);
              if (resultSet == null) {
                  HiLog.info(LABEL_LOG, "resultSet is null");
              }

              // 返回結(jié)果
              return resultSet;
          }


          • insert() 該方法接收兩個(gè)參數(shù),分別是插入的目標(biāo)路徑和插入的數(shù)據(jù)值。其中,插入的數(shù)據(jù)由ValuesBucket封裝,服務(wù)端可以從該參數(shù)中解析出對(duì)應(yīng)的屬性,然后插入到數(shù)據(jù)庫(kù)中。此方法返回一個(gè)int類型的值用于標(biāo)識(shí)結(jié)果。接收到傳過(guò)來(lái)的用戶信息并把它保存到數(shù)據(jù)庫(kù)中的代碼示例如下:

          public int insert(Uri uri, ValuesBucket value) {
              // 參數(shù)校驗(yàn)
              if (ormContext == null) {
                  HiLog.error(LABEL_LOG, "failed to insert, ormContext is null");
                  return -1;
              }

              // 構(gòu)造插入數(shù)據(jù)
              User user = new User();
              user.setUserId(value.getInteger("userId"));
              user.setFirstName(value.getString("firstName"));
              user.setLastName(value.getString("lastName"));
              user.setAge(value.getInteger("age"));
              user.setBalance(value.getDouble("balance"));

              // 插入數(shù)據(jù)庫(kù)
              boolean isSuccessful = ormContext.insert(user);
              if (!isSuccessful) {
                  HiLog.error(LABEL_LOG, "failed to insert");
                  return -1;
              }
              isSuccessful = ormContext.flush();
              if (!isSuccessful) {
                  HiLog.error(LABEL_LOG, "failed to insert flush");
                  return -1;
              }
              DataAbilityHelper.creator(this, uri).notifyChange(uri);
              int id = Math.toIntExact(user.getRowId());
              return id;
          }


          • batchInsert() 該方法為批量插入方法,接收一個(gè)ValuesBucket數(shù)組用于單次插入一組對(duì)象。它的作用是提高插入多條重復(fù)數(shù)據(jù)的效率。該方法系統(tǒng)已實(shí)現(xiàn),開(kāi)發(fā)者可以直接調(diào)用。
          • delete() 該方法用來(lái)執(zhí)行刪除操作。刪除條件由類DataAbilityPredicates構(gòu)建,服務(wù)端在接收到該參數(shù)之后可以從中解析出要?jiǎng)h除的數(shù)據(jù),然后到數(shù)據(jù)庫(kù)中執(zhí)行。根據(jù)傳入的條件刪除用戶表數(shù)據(jù)的代碼示例如下:

          public int delete(Uri uri, DataAbilityPredicates predicates) {
              if (ormContext == null) {
                  HiLog.error(LABEL_LOG, "failed to delete, ormContext is null");
                  return -1;
              }

              OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates,User.class);
              int value = ormContext.delete(ormPredicates);
              DataAbilityHelper.creator(this, uri).notifyChange(uri);
              return value;
          }


          • update() 此方法用來(lái)執(zhí)行更新操作。用戶可以在ValuesBucket參數(shù)中指定要更新的數(shù)據(jù),在DataAbilityPredicates中構(gòu)建更新的條件等。更新用戶表的數(shù)據(jù)的代碼示例如下:

          public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
              if (ormContext == null) {
                 HiLog.error(LABEL_LOG, "failed to update, ormContext is null");
                 return -1;
             }

             OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates,User.class);
             int index = ormContext.update(ormPredicates, value);
             HiLog.info(LABEL_LOG, "UserDataAbility update value:" + index);
             DataAbilityHelper.creator(this, uri).notifyChange(uri);
             return index;
          }


          • executeBatch() 此方法用來(lái)批量執(zhí)行操作。DataAbilityOperation中提供了設(shè)置操作類型、數(shù)據(jù)和操作條件的方法,用戶可自行設(shè)置自己要執(zhí)行的數(shù)據(jù)庫(kù)操作。該方法系統(tǒng)已實(shí)現(xiàn),開(kāi)發(fā)者可以直接調(diào)用。

          說(shuō)明 上述代碼示例中,初始化了數(shù)據(jù)庫(kù)類BookStore.class,并通過(guò)實(shí)體類User.class對(duì)該數(shù)據(jù)庫(kù)的表User進(jìn)行增刪改查操作。關(guān)于對(duì)象關(guān)系映射數(shù)據(jù)庫(kù)的具體邏輯,以及示例中BookStore.class與User.class的邏輯關(guān)系,可參考“對(duì)象關(guān)系映射數(shù)據(jù)庫(kù)開(kāi)發(fā)指導(dǎo)”。



          3


          注冊(cè)UserDataAbility


          和Service類似,開(kāi)發(fā)者必須在配置文件中注冊(cè)Data。配置文件中該字段在創(chuàng)建Data Ability時(shí)會(huì)自動(dòng)創(chuàng)建,name與創(chuàng)建的Data Ability一致。需要關(guān)注以下屬性:


          • type: 類型設(shè)置為data
          • uri: 對(duì)外提供的訪問(wèn)路徑,全局唯一
          • permissions: 訪問(wèn)該data ability時(shí)需要申請(qǐng)的訪問(wèn)權(quán)限 說(shuō)明 如果權(quán)限非系統(tǒng)權(quán)限,需要在配置文件中進(jìn)行自定義。請(qǐng)參考權(quán)限開(kāi)發(fā)指導(dǎo)中關(guān)于“自定義權(quán)限”的相關(guān)說(shuō)明。

          {
              "name"".UserDataAbility",
               "type""data",
               "visible"true,
               "uri""dataability://com.example.myapplication5.DataAbilityTest",
               "permissions": [
                  "com.example.myapplication5.DataAbility.DATA"
               ]
          }



          4


          訪問(wèn)Data


          開(kāi)發(fā)者可以通過(guò)DataAbilityHelper類來(lái)訪問(wèn)當(dāng)前應(yīng)用或其他應(yīng)用提供的共享數(shù)據(jù)。DataAbilityHelper作為客戶端,與提供方的Data進(jìn)行通信。Data接收到請(qǐng)求后,執(zhí)行相應(yīng)的處理,并返回結(jié)果。DataAbilityHelper提供了一系列與Data Ability對(duì)應(yīng)的方法。

          下面介紹DataAbilityHelper具體的使用步驟。


          聲明使用權(quán)限


          如果待訪問(wèn)的Data聲明了訪問(wèn)需要權(quán)限,則訪問(wèn)此Data需要在配置文件中聲明需要此權(quán)限。聲明請(qǐng)參考權(quán)限申請(qǐng)字段說(shuō)明。

          "reqPermissions": [
              {
                  "name""com.example.myapplication5.DataAbility.DATA"
              },
              // 訪問(wèn)文件還需要添加訪問(wèn)存儲(chǔ)讀寫權(quán)限
              {
                  "name""ohos.permission.READ_USER_STORAGE"
              },
              {
                  "name""ohos.permission.WRITE_USER_STORAGE"
              }
          ]


          創(chuàng)建DataAbilityHelper


          DataAbilityHelper為開(kāi)發(fā)者提供了creator()方法來(lái)創(chuàng)建DataAbilityHelper實(shí)例。該方法為靜態(tài)方法,有多個(gè)重載。最常見(jiàn)的方法是通過(guò)傳入一個(gè)context對(duì)象來(lái)創(chuàng)建DataAbilityHelper對(duì)象。


          獲取helper對(duì)象示例:


          DataAbilityHelper helper = DataAbilityHelper.creator(this);


          訪問(wèn)Data Ability


          DataAbilityHelper為開(kāi)發(fā)者提供了一系列的接口來(lái)訪問(wèn)不同類型的數(shù)據(jù)(文件、數(shù)據(jù)庫(kù)等)。


          • 訪問(wèn)文件 DataAbilityHelper為開(kāi)發(fā)者提供了FileDescriptor openFile(Uri uri, String mode)方法來(lái)操作文件。此方法需要傳入兩個(gè)參數(shù),其中uri用來(lái)確定目標(biāo)資源路徑,mode用來(lái)指定打開(kāi)文件的方式,可選方式包含“r”(讀), “w”(寫), “rw”(讀寫),“wt”(覆蓋寫),“wa”(追加寫),“rwt”(覆蓋寫且可讀)。該方法返回一個(gè)目標(biāo)文件的FD(文件描述符),把文件描述符封裝成流,開(kāi)發(fā)者就可以對(duì)文件流進(jìn)行自定義處理。訪問(wèn)文件示例:

          // 讀取文件描述符
          FileDescriptor fd = helper.openFile(uri, "r");
          FileInputStream fis = new FileInputStream(fd);
          // 使用文件描述符封裝成的文件流,進(jìn)行文件操作


          • 訪問(wèn)數(shù)據(jù)庫(kù) DataAbilityHelper為開(kāi)發(fā)者提供了增、刪、改、查以及批量處理等方法來(lái)操作數(shù)據(jù)庫(kù)。說(shuō)明 對(duì)數(shù)據(jù)庫(kù)的操作方法,詳見(jiàn)數(shù)據(jù)管理中各數(shù)據(jù)庫(kù)類型的開(kāi)發(fā)指南。

          方法描述
          ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates)查詢數(shù)據(jù)庫(kù)
          int insert(Uri uri, ValuesBucket value)向數(shù)據(jù)庫(kù)中插入單條數(shù)據(jù)
          int batchInsert(Uri uri, ValuesBucket[] values)向數(shù)據(jù)庫(kù)中插入多條數(shù)據(jù)
          int delete(Uri uri, DataAbilityPredicates predicates)刪除一條或多條數(shù)據(jù)
          int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates)更新數(shù)據(jù)庫(kù)
          DataAbilityResult[] executeBatch(ArrayListoperations)批量操作數(shù)據(jù)庫(kù)


          這些方法的使用說(shuō)明如下:


          • query() 查詢方法,其中uri為目標(biāo)資源路徑,columns為想要查詢的字段。開(kāi)發(fā)者的查詢條件可以通過(guò)DataAbilityPredicates來(lái)構(gòu)建。查詢用戶表中id在101-103之間的用戶,并把結(jié)果打印出來(lái),代碼示例如下:

          DataAbilityHelper helper = DataAbilityHelper.creator(this);
          // 構(gòu)造查詢條件
          DataAbilityPredicates predicates = new DataAbilityPredicates();
          predicates.between("userId"101103);
          // 進(jìn)行查詢
          ResultSet resultSet = helper.query(uri, columns, predicates);
          // 處理結(jié)果
          resultSet.goToFirstRow();
          do {
              // 在此處理ResultSet中的記錄;
          while(resultSet.goToNextRow());


          • insert() 新增方法,其中uri為目標(biāo)資源路徑,ValuesBucket為要新增的對(duì)象。插入一條用戶信息的代碼示例如下:

          DataAbilityHelper helper = DataAbilityHelper.creator(this);

          // 構(gòu)造插入數(shù)據(jù)
          ValuesBucket valuesBucket = new ValuesBucket();
          valuesBucket.putString("name""Tom");
          valuesBucket.putInteger("age"12);
          helper.insert(uri, valuesBucket);


          • batchInsert() 批量插入方法,和insert()類似。批量插入用戶信息的代碼示例如下:

          DataAbilityHelper helper = DataAbilityHelper.creator(this);
          // 構(gòu)造插入數(shù)據(jù)
          ValuesBucket[] values = new ValuesBucket[2];
          values[0] = new ValuesBucket();
          values[0].putString("name""Tom");
          values[0].putInteger("age"12);
          values[1] = new ValuesBucket();
          values[1].putString("name""Tom1");
          values[1].putInteger("age"16);
          helper.batchInsert(uri, values);


          • delete() 刪除方法,其中刪除條件可以通過(guò)DataAbilityPredicates來(lái)構(gòu)建。刪除用戶表中id在101-103之間的用戶,代碼示例如下:

          DataAbilityHelper helper = DataAbilityHelper.creator(this);
          // 構(gòu)造刪除條件
          DataAbilityPredicates predicates = new DataAbilityPredicates();
          predicates.between("userId"101103);
          helper.delete(uri, predicates);


          • update() 更新方法,更新數(shù)據(jù)由ValuesBucket傳入,更新條件由DataAbilityPredicates來(lái)構(gòu)建。更新id為102的用戶,代碼示例如下:

          DataAbilityHelper helper = DataAbilityHelper.creator(this);
          // 構(gòu)造更新條件
          DataAbilityPredicates predicates = new DataAbilityPredicates();
          predicates.equalTo("userId"102);
          // 構(gòu)造更新數(shù)據(jù)
          ValuesBucket valuesBucket = new ValuesBucket();
          valuesBucket.putString("name""Tom");
          valuesBucket.putInteger("age"12);
          helper.update(uri, valuesBucket, predicates);


          • executeBatch() 此方法用來(lái)執(zhí)行批量操作。DataAbilityOperation中提供了設(shè)置操作類型、數(shù)據(jù)和操作條件的方法,開(kāi)發(fā)者可自行設(shè)置自己要執(zhí)行的數(shù)據(jù)庫(kù)操作。插入多條數(shù)據(jù)的代碼示例如下:

          DataAbilityHelper helper = DataAbilityHelper.creator(abilityObj, insertUri);
          // 構(gòu)造批量操作
          ValuesBucket value1 = initSingleValue();
          DataAbilityOperation opt1 = DataAbilityOperation.newInsertBuilder(insertUri).withValuesBucket(value1).build();
          ValuesBucket value2 = initSingleValue2();
          DataAbilityOperation opt2 = DataAbilityOperation.newInsertBuilder(insertUri).withValuesBucket(value2).build();
          ArrayList<DataAbilityOperation> operations = new ArrayList<DataAbilityOperation>();
          operations.add(opt1);
          operations.add(opt2);
          DataAbilityResult[] result = helper.executeBatch(insertUri, operations);



          5


          相關(guān)實(shí)例



          針對(duì)Data Ability開(kāi)發(fā),有以下示例工程可供參考:


          • DataAbility 本示例演示了如何使用Data Ability對(duì)數(shù)據(jù)庫(kù)進(jìn)行增、刪、改、查,以及讀取文本文件。針對(duì)Data Ability開(kāi)發(fā),有以下Codelabs可供參考:

          • 關(guān)系型數(shù)據(jù)庫(kù) 基于Data Ability的關(guān)系型數(shù)據(jù)庫(kù)和數(shù)據(jù)管理能力,實(shí)現(xiàn)數(shù)據(jù)庫(kù)相關(guān)應(yīng)用服務(wù)的快速開(kāi)發(fā)。


          往期推薦



          點(diǎn)擊閱讀原文,更精彩~
          瀏覽 70
          點(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>
                  亚洲AV成人精品一区二区三区_ | 久国产视频。 | 天天操天天干天天插 | 国精品人伦一区二区三区蜜桃 | 成人无码片黄网站A毛片免费 |