HarmonyOS Sample之DataAbility RDB数据库操作

数据库 其他数据库 OpenHarmony
使用Data模板的Ability(以下简称“Data”)有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。

[[414085]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

DataAbility RDB数据库操作

介绍

使用Data模板的Ability(以下简称“Data”)有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。Data既可用于同设备不同应用的数据共享,也支持跨设备不同应用的数据共享。

数据的存放形式多样,可以是数据库,也可以是磁盘上的文件。Data对外提供对数据的增、删、改、查,以及打开文件等接口,这些接口的具体实现由开发者提供。

本示例演示了如何使用Data Ability对RDB数据库进行增、删、改、查,以及读取文本文件。

模仿手机的备忘录,实现了简单的操作。

搭建环境

安装DevEco Studio,详情请参考DevEco Studio下载

设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作

如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

步骤

1.创建一个DataAbility和数据库常量类

a.创建一个Empty DataAbility

entity右键,New- Ability-Empty Data Ability,然后输入名称 NoteDataAbility

HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙HarmonyOS技术社区

b.创建一个数据库常量类 Const.java

存放数据库名称、表名称、字段列名称、存储路径等

需要注意的是,

BASE_URI 3个杠后面的部分要和config.json Data Ability 声明的uri完全一致,否则应用无法启动

  1. /** 
  2.  * Const 
  3.  */ 
  4. public class Const { 
  5.     /** 
  6.      * DataAbility base uri 
  7.      * scheme:协议方案名,固定为“dataability”,代表Data Ability所使用的协议类型。 
  8.      * authority:设备ID。如果为跨设备场景,则为目标设备的ID;如果为本地设备场景,则不需要填写。 
  9.      * path:资源的路径信息,代表特定资源的位置信息。 
  10.      * query:查询参数。 
  11.      * fragment:可以用于指示要访问的子资源。 
  12.      * 本地设备的“device_id”字段为空,因此在“dataability:”后面有三个“/” 
  13.      * 
  14.      * BASE_URI 3个杠后面的部分要和config.json  Data Ability 声明的uri完全一致,否则应用无法启动 
  15.      * 
  16.      */ 
  17.     public static final String BASE_URI = "dataability:///ohos.samples.dataability.NoteDataAbility"
  18.  
  19.     /** 
  20.      * Database name 
  21.      */ 
  22.     public static final String DB_NAME = "note.db"
  23.  
  24.     /** 
  25.      * Database table name 
  26.      */ 
  27.     public static final String DB_TAB_NAME = "note"
  28.  
  29.     /** 
  30.      * Database column name:Id 
  31.      */ 
  32.     public static final String DB_COLUMN_ID = "Id"
  33.     /** 
  34.      * Database column name:noteTitle 
  35.      */ 
  36.     public static final String DB_COLUMN_TITLE = "noteTitle"
  37.  
  38.     /** 
  39.      * Database column name:writeTime 
  40.      */ 
  41.     public static final String DB_COLUMN_TIME = "writeTime"
  42.  
  43.     /** 
  44.      * Database column name:noteCategory 
  45.      */ 
  46.     public static final String DB_COLUMN_CATEGORY = "noteCategory"
  47.     /** 
  48.      * Database column name:noteContent 
  49.      */ 
  50.     public static final String DB_COLUMN_CONTENT = "noteContent"
  51.  
  52.     /** 
  53.      * Database data path 
  54.      */ 
  55.     public static final String DATA_PATH = "/note"
  56.  
  57.  
  58.     /** 
  59.      * 文件名称 
  60.      */ 
  61.     public static final String FILE_NAME = "userdataability.txt"
  62.  

c.config.json相关配置

config.json涉及NoteDataAbility.java 的地方有3处,

第1处在module对象下,

HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙HarmonyOS技术社区

第2处是abilities对象下,

permissions表示其他应用的能力调用当前能力所需的权限。

默认情况下隐藏"visible"字段(值为false),表示仅本应用可访问该Data,开发人员可根据需求修改permissions、visible值、uri等内容。当外部应用需要访问/控制此数据库字段时,在该Data Ability配置中增加"visible": true,并在外面应用的配置文件config.json中申请permissions权限。

HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙HarmonyOS技术社区

第3处是reqPermissions对象下,

说明:如果待访问的Data Ability是由本应用创建,则可以不声明该权限。

HarmonyOS Sample 之 DataAbility RDB数据库操作-鸿蒙HarmonyOS技术社区

2.声明数据库存储对象和数据库配置

在NoteDataAbility.java 添加如下代码

  1. //声明数据库存储对象 
  2. private RdbStore rdbStore; 
  3. //数据库配置,指定数据库名称 
  4. private StoreConfig storeConfig = StoreConfig.newDefaultConfig(Const.DB_NAME); 

3.实现打开RDB数据库回调函数

在NoteDataAbility.java 添加如下代码

  1. // 管理数据库创建、升级和降级。 
  2. // 您可以创建一个子类来实现 #onCreate、#onUpgrade 或 #onOpen 方法。  
  3. // 如果一个数据库已经存在,它将被打开; 如果不存在数据库,则将创建一个数据库。  
  4. // 在数据库升级过程中,也会调用该类的方法。 
  5. private RdbOpenCallback rdbOpenCallback = new RdbOpenCallback() { 
  6.     @Override 
  7.     public void onCreate(RdbStore rdbStore) { 
  8.         //创建表 
  9.         rdbStore.executeSql( 
  10.                 "create table if not exists " + Const.DB_TAB_NAME + "2 (" + 
  11.                         Const.DB_COLUMN_ID + " integer primary key autoincrement ," + 
  12.                         Const.DB_COLUMN_TITLE + " text not null," + 
  13.                         Const.DB_COLUMN_CONTENT + " text not null," + 
  14.                         Const.DB_COLUMN_TIME + " text not null," + 
  15.                         Const.DB_COLUMN_CATEGORY + " text not null" + 
  16.  
  17.                         ")" 
  18.         ); 
  19.     } 
  20.  
  21.     @Override 
  22.     public void onUpgrade(RdbStore rdbStore, int i, int i1) { 
  23.         //数据库升级 
  24.     } 
  25. }; 

4.初始化RDB数据库存储对象

在NoteDataAbility.java 添加如下代码

  1. @Override 
  2. public void onStart(Intent intent) { 
  3.     super.onStart(intent); 
  4.     HiLog.info(LABEL_LOG, "NoteDataAbility onStart"); 
  5.     //数据库帮助类 
  6.     DatabaseHelper databaseHelper = new DatabaseHelper(this); 
  7.     //初始化RDB数据库存储对象 
  8.     rdbStore = databaseHelper.getRdbStore(storeConfig, 1, rdbOpenCallback); 
  9.  

5.实现对数据库的基本操作函数

NoteDataAbility.java操作数据库的方法都需要自己实现,包括:添加、修改、查询、删除,还有打开文件,主要使用rdbStore对象。

  1. @Override 
  2. public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) { 
  3.     HiLog.info(LABEL_LOG, "NoteDataAbility query"); 
  4.     RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, Const.DB_TAB_NAME); 
  5.     return rdbStore.query(rdbPredicates, columns); 
  6.  
  7. @Override 
  8. public int insert(Uri uri, ValuesBucket value) { 
  9.     HiLog.info(LABEL_LOG, "NoteDataAbility insert"); 
  10.     //long to int 
  11.     int rowId = (int) rdbStore.insert(Const.DB_TAB_NAME, value); 
  12.     //通知观察者数据发生变化 
  13.     DataAbilityHelper.creator(this).notifyChange(uri); 
  14.     return rowId; 
  15.  
  16. @Override 
  17. public int delete(Uri uri, DataAbilityPredicates predicates) { 
  18.     //rdb 条件,通过DataAbilityUtils将DataAbilityPredicates转成 RdbPredicates 
  19.     RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, Const.DB_TAB_NAME); 
  20.     //执行删除 
  21.     int rowId = rdbStore.delete(rdbPredicates); 
  22.     HiLog.info(LABEL_LOG, "%{public}s""delete"); 
  23.     //通知观察者数据发生变化 
  24.     DataAbilityHelper.creator(this).notifyChange(uri); 
  25.     return rowId; 
  26.  
  27. @Override 
  28. public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) { 
  29.     //rdb 条件,通过DataAbilityUtils将DataAbilityPredicates转成 RdbPredicates 
  30.     RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, Const.DB_TAB_NAME); 
  31.     int rowId =rdbStore.update(value, rdbPredicates); 
  32.     //通知观察者数据发生变化 
  33.     DataAbilityHelper.creator(this).notifyChange(uri); 
  34.     return rowId; 
  35.  
  36. @Override 
  37. public FileDescriptor openFile(Uri uri, String mode) { 
  38.  
  39.     //获取应用程序在设备内部存储器上存放文件的目录 
  40.     File file = new File(getFilesDir(), uri.getDecodedQuery()); 
  41.  
  42.     FileDescriptor fileDescriptor = null
  43.     try { 
  44.         FileInputStream fis = new FileInputStream(file); 
  45.         //获取FD 
  46.         fileDescriptor = fis.getFD(); 
  47.         //获取一个新的文件描述符,它是现有文件描述符的副本 
  48.         return MessageParcel.dupFileDescriptor(fileDescriptor); 
  49.     } catch (IOException e) { 
  50.         e.printStackTrace(); 
  51.     } 
  52.     return fileDescriptor; 

6.数据的订阅和通知

在NoteDataAbility.java 中, 我们看到insert/update/delete方法都有一行。

  1. DataAbilityHelper.creator(this).notifyChange(uri); 

目的是在数据库数据发生变化时,通知数据的订阅者。

而在MainAbilitySlice.java 类中有如下方法,在OnStart()中被调用,实现了数据变化的订阅。

  1. private void initDatabaseHelper() { 
  2.     //创建实例 
  3.     dataAbilityHelper = DataAbilityHelper.creator(this); 
  4.     //注册一个观察者来观察给定 Uri 指定的数据,dataObserver表示 IDataAbilityObserver 对象 
  5.     dataAbilityHelper.registerObserver(Uri.parse(Const.BASE_URI), dataAbilityObserver); 

同时,数据变化订阅方还需要实现IDataAbilityObserver接口,在数据变化时会自动回调,完成对应的逻辑处理。

  1. //观察者模式,数据变化时回调 
  2. private final IDataAbilityObserver dataAbilityObserver=() -> { 
  3.     HiLog.info(LABEL, "%{public}s""database changed"); 
  4.     //筛选数据 
  5.     initLists(this); 
  6. }; 

当数据订阅者不再需要订阅Data变化时,则调用unregisterObserver​(Uri uri, IDataAbilityObserver dataObserver)方法取消。

  1. @Override 
  2. protected void onStop() { 
  3.     super.onStop(); 
  4.     dataAbilityHelper.unregisterObserver(Uri.parse(Const.BASE_URI), dataAbilityObserver); 

观察者模式的作用在于当数据库表格的内容产生变化时,可以主动通知与该表格数据相关联的进程或者应用,从而使得相关进程或者应用接收到数据变化后完成相应的处理。

7.访问Data Ability,新建AddNoteAbility,在AddNoteAbilitySlice实现数据的添加和修改

开发者可以通过DataAbilityHelper类来访问当前应用或其他应用提供的共享数据。

DataAbilityHelper作为客户端,与提供方的Data进行通信。DataAbilityHelper提供了一系列与Data Ability通信的方法。

a.数据的添加

  1. /** 
  2.  * 保存数据 
  3.  * 
  4.  * @param component component 
  5.  */ 
  6. private void saveNote(Component component) { 
  7.     ValuesBucket valuesBucket = new ValuesBucket(); 
  8.     TextField noteTitle = (TextField) findComponentById(ResourceTable.Id_add_note_title); 
  9.     if (noteTitle.getText().isEmpty()) { 
  10.         DialLogUtils dialog = new DialLogUtils(this, "标题不能为空!"); 
  11.         dialog.showDialog(); 
  12.         return
  13.     } 
  14.     TextField noteContent = (TextField) findComponentById(ResourceTable.Id_add_note_content); 
  15.     if (noteContent.getText().isEmpty()) { 
  16.         DialLogUtils dialog = new DialLogUtils(this, "内容不能为空!"); 
  17.         dialog.showDialog(); 
  18.         return
  19.     } 
  20.     Text noteCategory = (Text) findComponentById(ResourceTable.Id_add_note_category); 
  21.     Text noteTime = (Text) findComponentById(ResourceTable.Id_add_note_time); 
  22.  
  23.     HiLog.debug(LABEL, "%{public}s""saveNote, noteId:[" + noteId + "],noteCategory:" + noteCategory.getText()); 
  24.     int rowId; 
  25.     //放入键值 
  26.     valuesBucket.putString(Const.DB_COLUMN_TITLE, noteTitle.getText()); 
  27.     valuesBucket.putString(Const.DB_COLUMN_CATEGORY, noteCategory.getText()); 
  28.     valuesBucket.putString(Const.DB_COLUMN_CONTENT, noteContent.getText()); 
  29.     valuesBucket.putString(Const.DB_COLUMN_TIME, noteTime.getText()); 
  30.     try { 
  31.         if (noteId.isEmpty()) { 
  32.             HiLog.debug(LABEL, "%{public}s""saveNote, insert"); 
  33.             //插入数据 
  34.             rowId = dataAbilityHelper.insert(Uri.parse(Const.BASE_URI + Const.DATA_PATH), valuesBucket); 
  35.             HiLog.debug(LABEL, "%{public}s""insert,rowId:" + rowId); 
  36.  
  37.         } else { 
  38.             HiLog.debug(LABEL, "%{public}s""saveNote, update"); 
  39.             //指定修改谓语 
  40.             DataAbilityPredicates predicates = new DataAbilityPredicates(); 
  41.             predicates.equalTo(Const.DB_COLUMN_ID, noteId); 
  42.             //修改数据 
  43.             rowId = dataAbilityHelper.update(Uri.parse(Const.BASE_URI + Const.DATA_PATH), valuesBucket, predicates); 
  44.             HiLog.debug(LABEL, "%{public}s""update,rowId:" + rowId); 
  45.         } 
  46.  
  47.         //返回列表页 
  48.         backListPage(); 
  49.     } catch (DataAbilityRemoteException | IllegalStateException exception) { 
  50.         HiLog.error(LABEL, "%{public}s""insert: dataRemote exception|illegalStateException"); 
  51.     } 

b.修改和删除数据

  1. @Override 
  2. public void onStart(Intent intent) { 
  3.     super.onStart(intent); 
  4.     //设置UI布局资源 
  5.     super.setUIContent(ResourceTable.Layout_ability_add_note); 
  6.     // 
  7.     initDatabaseHelper(); 
  8.     //返回按钮 
  9.     Component backButton = findComponentById(ResourceTable.Id_back_image); 
  10.     backButton.setClickedListener(component -> terminateAbility()); 
  11.     TextField noteContent = (TextField) findComponentById(ResourceTable.Id_add_note_content); 
  12.  
  13.  
  14.     //修改笔记 
  15.     if (intent.hasParameter("Id")) { 
  16.         HiLog.info(LABEL, "%{public}s""change data coming"); 
  17.         noteId = intent.getStringParam("Id"); 
  18.         HiLog.info(LABEL, "%{public}s""noteId:" + noteId); 
  19.         if (noteId != null) { 
  20.             DataAbilityPredicates predicates = new DataAbilityPredicates(); 
  21.             predicates.equalTo(Const.DB_COLUMN_ID, noteId); 
  22.             //查询数据 
  23.             NoteListItemInfo itemInfo = queryOne(predicates); 
  24.             HiLog.info(LABEL, "%{public}s""noteTitle:" + itemInfo.getNoteTitle() + ",category:" + itemInfo.getNoteCategory()); 
  25.             //设置显示 
  26.             TextField noteTitle = (TextField) findComponentById(ResourceTable.Id_add_note_title); 
  27.             noteTitle.setText(itemInfo.getNoteTitle()); 
  28.  
  29.             noteContent.setText(itemInfo.getNoteContent()); 
  30.  
  31.             Text category = (Text) findComponentById(ResourceTable.Id_add_note_category); 
  32.             category.setText(itemInfo.getNoteCategory()); 
  33.  
  34.             Text noteTime = (Text) findComponentById(ResourceTable.Id_add_note_time); 
  35.             noteTime.setText(itemInfo.getNoteTime()); 
  36.  
  37.  
  38.             Component deleteButton = findComponentById(ResourceTable.Id_delete_image); 
  39.             //设置删除按钮可用,只有修改笔记才能删除 
  40.             deleteButton.setClickable(true); 
  41.             //添加事件 
  42.             deleteButton.setClickedListener(component -> { 
  43.                 try { 
  44.                     int rowId = dataAbilityHelper.delete(Uri.parse(Const.BASE_URI + Const.DATA_PATH), predicates); 
  45.                     HiLog.info(LABEL, "%{public}s""deleteNote,rowId:" + rowId); 
  46.  
  47.                     //返回列表页 
  48.                     backListPage(); 
  49.                 } catch (DataAbilityRemoteException e) { 
  50.                     HiLog.error(LABEL, "%{public}s""delete: exception|DataAbilityRemoteException"); 
  51.                 } 
  52.             }); 
  53.         } 
  54.     } else { 
  55.         Text timeText = (Text) findComponentById(ResourceTable.Id_add_note_time); 
  56.         String time24 = sdf.format(new Date()); 
  57.         timeText.setText(time24); 
  58.     } 
  59.  
  60.     //保存笔记 
  61.     Component insertButton = findComponentById(ResourceTable.Id_finish_image); 
  62.     insertButton.setClickedListener(this::saveNote); 
  63.  

c.查询数据

  1. private NoteListItemInfo queryOne(DataAbilityPredicates predicates) { 
  2.     HiLog.info(LABEL, "%{public}s""database query"); 
  3.     String[] columns = new String[]{ 
  4.             Const.DB_COLUMN_ID, 
  5.             Const.DB_COLUMN_TITLE, Const.DB_COLUMN_TIME, 
  6.             Const.DB_COLUMN_CATEGORY, Const.DB_COLUMN_CONTENT}; 
  7.     try { 
  8.         ResultSet resultSet = dataAbilityHelper.query( 
  9.                 Uri.parse(Const.BASE_URI + Const.DATA_PATH), columns, predicates); 
  10.  
  11.         //无数据 
  12.         if (resultSet.getRowCount() == 0) { 
  13.             HiLog.info(LABEL, "%{public}s""query:No result found"); 
  14.             return null
  15.         } 
  16.         // 
  17.         resultSet.goToFirstRow(); 
  18.         //根据列索引获取列值 
  19.         String noteId = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_ID)); 
  20.         String noteTitle = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_TITLE)); 
  21.         String noteTime = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_TIME)); 
  22.         String noteCategory = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_CATEGORY)); 
  23.         String noteContent = resultSet.getString(resultSet.getColumnIndexForName(Const.DB_COLUMN_CONTENT)); 
  24.         Element image = ElementScatter.getInstance(getContext()).parse(ResourceTable.Graphic_icon_nodata); 
  25.         HiLog.info(LABEL, "%{public}s""set  show:" + noteCategory); 
  26.         // 
  27.         return new NoteListItemInfo(noteId, noteTitle, noteContent, noteTime, noteCategory, image); 
  28.  
  29.     } catch (DataAbilityRemoteException | IllegalStateException exception) { 
  30.         HiLog.error(LABEL, "%{public}s""query: dataRemote exception|illegalStateException"); 
  31.     } 
  32.     return null

实践中遇到的小知识点记录一下

1. 如何监听 TextField 文本变更事件

  1. /** 
  2.  * 监听TextFiled 文本变化 
  3.  */ 
  4. private void initSearchBtnEvent(AbilitySlice slice) { 
  5.     TextField searchTF = (TextField) findComponentById(ResourceTable.Id_tf_note_search); 
  6.     //添加文本观察器 TextObserver 以检测文本是否发生更改。 
  7.     searchTF.addTextObserver(new Text.TextObserver() { 
  8.         @Override 
  9.         public void onTextUpdated(String s, int i, int i1, int i2) { 
  10.             HiLog.info(LABEL, "addTextObserver 按键事件触发....."); 
  11.             //筛选数据 
  12.             initLists(slice); 
  13.         } 
  14.     }); 

2. ListContainer 组件添加点击事件

在 Provider 中 getComponent添加,在初始化Provider时传递AbilitySlice对象过来

  1. public ListItemProvider(List<ItemInfo> itemList, AbilityContext context,AbilitySlice slice) { 
  2.     this.itemList = itemList; 
  3.     this.context = context; 
  4.     this.typeFactory = new ListTypeFactory(); 
  5.     this.slice=slice; 
  6. @Override 
  7. public Component getComponent(int index, Component component, ComponentContainer componentContainer) { 
  8.     Component itemComponent = component; 
  9.     ViewHolder viewHolder; 
  10.     if (itemComponent == null) { 
  11.         itemComponent = LayoutScatter.getInstance(componentContainer.getContext()) 
  12.                 .parse(getItemComponentType(index), componentContainer, false); 
  13.  
  14.     } 
  15.     viewHolder = typeFactory.getViewHolder(getItemComponentType(index), itemComponent); 
  16.     viewHolder.setUpComponent(getItem(index), context); 
  17.  
  18.  
  19.     //设置点击事件 
  20.     itemComponent.setClickedListener(component1 -> { 
  21.         //获取noteId 
  22.         String noteId=""
  23.         if(getItem(index) instanceof NoteListItemInfo){ 
  24.             //HiLog.debug(LABEL, "%{public}s""ItemInfo instanceof SingleButtonDoubleLineListItemInfo"); 
  25.             noteId=((NoteListItemInfo)getItem(index)).getNoteId(); 
  26.         } 
  27.         HiLog.debug(LABEL, "%{public}s""noteId:" + noteId); 
  28.         //1.携带笔记ID参数,跳转到AddNoteAbilitySlice 
  29.         Intent intent = new Intent(); 
  30.         if(noteId!=null){ 
  31.             //保存要传递的参数 
  32.             intent.setParam("Id", noteId); 
  33.             Operation operation = new Intent.OperationBuilder() 
  34.                     .withDeviceId(""
  35.                     .withBundleName("com.buty.samples"
  36.                     .withAbilityName(AddNoteAbility.class).build(); 
  37.             intent.setOperation(operation); 
  38.  
  39.             slice.startAbility(intent); 
  40.         }else { 
  41.             HiLog.error(LABEL, "%{public}s""noteId is null"); 
  42.         } 
  43.     }); 
  44.  
  45.     return itemComponent; 

效果展示

文章相关附件可以点击下面的原文链接前往下载。

原文链接:https://harmonyos.51cto.com/posts/7386

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2022-04-08 11:25:58

数据库操作AbilityData

2009-08-19 16:40:26

C#操作Access数

2021-09-06 10:24:12

鸿蒙HarmonyOS应用

2011-07-12 15:22:04

SQLite数据库

2010-08-18 13:49:01

DB2数据库

2021-09-03 15:41:00

鸿蒙HarmonyOS应用

2011-08-02 16:43:26

iPhone开发 Ssqlite3 数据库

2021-08-17 10:20:14

鸿蒙HarmonyOS应用

2021-09-15 14:55:49

鸿蒙HarmonyOS应用

2022-08-01 13:59:04

数据库通信ArkUI

2010-09-30 09:11:01

2010-09-30 08:27:48

2021-09-17 14:43:54

鸿蒙HarmonyOS应用

2021-09-24 09:25:01

鸿蒙HarmonyOS应用

2010-09-27 14:54:38

SQL数据库

2010-08-31 16:53:54

DHCP数据库

2021-07-28 14:40:57

鸿蒙HarmonyOS应用

2021-11-23 09:58:35

鸿蒙HarmonyOS应用

2021-05-24 09:08:50

数据库工具技术

2011-09-23 09:09:38

数据库迁移
点赞
收藏

51CTO技术栈公众号