diff --git a/app/build.gradle b/app/build.gradle
index 69bf5cd0..a6212aa7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,8 +3,8 @@ plugins {
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
+ id 'realm-android'
}
-
android {
namespace 'com.navinfo.omqs'
compileSdk 33
@@ -26,8 +26,8 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '1.8'
@@ -50,8 +50,12 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- //权限管理
- implementation 'com.github.getActivity:XXPermissions:16.5'
+ //权限管理 https://github.com/getActivity/XXPermissions
+ implementation 'com.github.getActivity:XXPermissions:16.8'
+ // 文件管理 https://github.com/K1rakishou/Fuck-Storage-Access-Framework
+ implementation 'com.github.K1rakishou:Fuck-Storage-Access-Framework:v1.1.3'
+ // Android工具类库 https://blankj.com/2016/07/31/android-utils-code/
+ implementation 'com.blankj:utilcodex:1.30.1'
//依赖注入
//hilt
implementation "com.google.dagger:hilt-android:2.44"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4e0cee42..f32f5edd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,11 +34,10 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
- android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/Theme.OMQualityInspection"
- tools:targetApi="31">
+ android:requestLegacyExternalStorage="true">
+
\ No newline at end of file
diff --git a/app/src/main/assets/MergeOMDB.py b/app/src/main/assets/MergeOMDB.py
new file mode 100644
index 00000000..401524b1
--- /dev/null
+++ b/app/src/main/assets/MergeOMDB.py
@@ -0,0 +1,95 @@
+# coding:utf-8
+# 合并指定目录下的omdb(sqlite)数据
+
+import os
+import sys
+import json
+import sqlite3
+
+
+# 定义遍历目录的函数
+def traverse_dir(path):
+ fileList = list()
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ if str(file).endswith(".omdb"):
+ # 文件的完整路径
+ file_path = os.path.join(root, file)
+ # 处理文件,例如读取文件内容等
+ print(file_path)
+ fileList.append(file_path)
+ return fileList
+
+
+# 打开配置文件,读取用户配置的
+def openConfigJson(path):
+ # 读取json配置,获取要抽取的表名
+ with open(path, "r") as f:
+ configMap = json.load(f)
+ return configMap
+
+
+# 按照tableList中指定的表名合并多个源数据库到指定目标数据库中
+def mergeSqliteData(originSqliteList, destSqlite, tableList):
+ destConn = sqlite3.connect(destSqlite)
+ destCursor = destConn.cursor()
+
+ for originSqlite in originSqliteList:
+ originConn = sqlite3.connect(originSqlite)
+ originCursor = originConn.cursor()
+ # 从源数据库中遍历取出表list中的数据
+ for table in tableList:
+ # 检查目标数据库中是否存在指定的表
+ containsTable = destCursor.execute(
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'" % (table)).fetchall()
+ if not containsTable or len(containsTable) <= 0:
+ # 复制表结构
+ originCursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'" % (table))
+ createTableSql = originCursor.fetchone()[0]
+ destCursor.execute(createTableSql)
+ destConn.commit()
+
+ originCursor.execute("Select * From " + table)
+ # 获取到源数据库中该表的所有数据
+ originData = originCursor.fetchall()
+ # 获取一行数据中包含多少列,以此动态设置sql语句中的?个数
+ if originData and len(originData)>0:
+ num_cols = len(originData[0])
+ placeholders = ",".join(["?"] * num_cols)
+ for row in originData:
+ destCursor.execute("INSERT INTO "+table+" VALUES ({})".format(placeholders), row)
+
+ print("{}数据已导入!".format(originSqlite))
+ originCursor.close()
+ originConn.close()
+ destConn.commit()
+ destCursor.close()
+ destConn.close()
+
+
+if __name__ == '__main__':
+ params = sys.argv[1:] # 截取参数
+ if params:
+ if not params[0]:
+ print("请输入要合并的omdb数据的文件夹")
+ raise AttributeError("请输入要合并的omdb数据的文件夹")
+ # 获取导出文件的表配置
+ jsonPath = params[0] + "/config.json"
+ if not os.path.exists(jsonPath):
+ raise AttributeError("指定目录下缺少config.json配置文件")
+ omdbDir = params[0]
+ originSqliteList = traverse_dir(omdbDir) # 获取到所有的omdb数据库的路径
+
+ tableNameList = list()
+ configMap = openConfigJson(jsonPath)
+ if configMap["tables"] and len(configMap["tables"]) > 0:
+ for tableName in set(configMap["tables"]):
+ tableNameList.append(tableName)
+ print(tableNameList)
+ else:
+ raise AttributeError("config.json文件中没有配置抽取数据的表名")
+
+ # 开始分别连接Sqlite数据库,按照指定表名合并数据
+ mergeSqliteData(originSqliteList, params[0]+"/output.sqlite", tableNameList)
+ else:
+ raise AttributeError("缺少参数:请输入要合并的omdb数据的文件夹")
diff --git a/app/src/main/java/com/navinfo/omqs/Constant.kt b/app/src/main/java/com/navinfo/omqs/Constant.kt
index bc53b658..446bfc16 100644
--- a/app/src/main/java/com/navinfo/omqs/Constant.kt
+++ b/app/src/main/java/com/navinfo/omqs/Constant.kt
@@ -1,5 +1,7 @@
package com.navinfo.omqs
+import io.realm.Realm
+
class Constant {
companion object {
/**
@@ -7,7 +9,9 @@ class Constant {
*/
lateinit var ROOT_PATH: String
lateinit var MAP_PATH: String
+ lateinit var DATA_PATH: String
lateinit var OFFLINE_MAP_PATH: String
+
/**
* 服务器地址
*/
@@ -20,7 +24,7 @@ class Constant {
const val message_version_right_off = "1" //立即发送
const val MESSAGE_PAGE_SIZE = 30 //消息列表一页最多数量
-
+ lateinit var realm: Realm
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
index 7e85a078..7205a54d 100644
--- a/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
+++ b/app/src/main/java/com/navinfo/omqs/OMQSApplication.kt
@@ -2,7 +2,12 @@ package com.navinfo.omqs
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
+import io.realm.Realm
@HiltAndroidApp
class OMQSApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ Realm.init(this)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
index 860173a2..d4485441 100644
--- a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
+++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityBean.kt
@@ -1,30 +1,37 @@
package com.navinfo.omqs.bean
-data class OfflineMapCityBean(
- val id: String,
- /**
- * 文件名称
- */
- val fileName: String,
- /**
- * 城市名称
- */
- val name: String,
- val url: String,
- val version: Long,
- var fileSize: Long,
- var currentSize:Long = 0,
- var status:Int = NONE
-) {
- companion object Status{
- const val NONE = 0 //无状态
- const val WAITING = 1 //等待中
- const val LOADING = 2 //下载中
- const val PAUSE = 3 //暂停
- const val ERROR = 4 //错误
- const val DONE = 5 //完成
- const val UPDATE = 6 //有新版本要更新
- }
+import io.realm.RealmObject
+
+enum class StatusEnum(val status: Int) {
+ NONE(0), WAITING(1), LOADING(2), PAUSE(3),
+ ERROR(4), DONE(5), UPDATE(6)
+}
+
+open class OfflineMapCityBean{
+ var id: String = ""
+ var fileName: String = ""
+ var name: String = ""
+ var url: String = ""
+ var version: Long = 0L
+ var fileSize: Long = 0L
+ var currentSize:Long = 0L
+ var status: Int = StatusEnum.NONE.status
+
+ // status的转换对象
+ var statusEnum:StatusEnum
+ get() {
+ return try {
+ StatusEnum.values().find { it.status == status }!!
+ } catch (e: IllegalArgumentException) {
+ StatusEnum.NONE
+ }
+ }
+ set(value) {
+ status = value.status
+ }
+
+ constructor() : super()
+
fun getFileSizeText(): String {
return if (fileSize < 1024.0)
"$fileSize B"
diff --git a/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt
new file mode 100644
index 00000000..92461d60
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/bean/OfflineMapCityRealmObject.kt
@@ -0,0 +1,17 @@
+package com.navinfo.omqs.bean
+
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+
+
+open class OfflineMapCityRealmObject(){
+ @PrimaryKey
+ var id: String = ""
+ var fileName: String=""
+ var name: String = ""
+ var url: String = ""
+ var version: Long = 0
+ var fileSize: Long = 0
+ var currentSize:Long = 0
+ var status:Int = 0
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt b/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt
new file mode 100644
index 00000000..581343c3
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/data/process/DataEngine.kt
@@ -0,0 +1,8 @@
+package com.navinfo.omqs.data.process
+
+/**
+ * 数据处理引擎,数据导入、导出及转换处理
+ * */
+class DataEngine {
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt
new file mode 100644
index 00000000..b0293ec3
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/ui/MainActivity.kt
@@ -0,0 +1,90 @@
+package com.navinfo.omqs.ui
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.view.WindowCompat
+import androidx.navigation.findNavController
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.navigateUp
+import androidx.navigation.ui.setupActionBarWithNavController
+import android.view.Menu
+import android.view.MenuItem
+import com.github.k1rakishou.fsaf.FileChooser
+import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
+import com.navinfo.omqs.R
+import com.navinfo.omqs.databinding.ActivityMainBinding
+import com.navinfo.omqs.ui.activity.PermissionsActivity
+
+class MainActivity : PermissionsActivity(), FSAFActivityCallbacks {
+
+ private lateinit var appBarConfiguration: AppBarConfiguration
+ private lateinit var binding: ActivityMainBinding
+ private val fileChooser by lazy { FileChooser(this@MainActivity) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+// val navController = findNavController(R.id.nav_host_fragment_content_main)
+// appBarConfiguration = AppBarConfiguration(navController.graph)
+// setupActionBarWithNavController(navController, appBarConfiguration)
+
+ fileChooser.setCallbacks(this@MainActivity)
+// binding.fab.setOnClickListener { view ->
+// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+// .setAnchorView(R.id.fab)
+// .setAction("Action", null).show()
+// // 开始数据导入功能
+// fileChooser.openChooseFileDialog(object: FileChooserCallback() {
+// override fun onCancel(reason: String) {
+// }
+//
+// override fun onResult(uri: Uri) {
+// val file = UriUtils.uri2File(uri)
+// Snackbar.make(view, "文件大小为:${file.length()}", Snackbar.LENGTH_LONG)
+// .show()
+// }
+// })
+// }
+ }
+
+ override fun onPermissionsGranted() {
+ }
+
+ override fun onPermissionsDenied() {
+ }
+
+// override fun onCreateOptionsMenu(menu: Menu): Boolean {
+// // Inflate the menu; this adds items to the action bar if it is present.
+// menuInflater.inflate(R.menu.menu_main, menu)
+// return true
+// }
+
+// override fun onOptionsItemSelected(item: MenuItem): Boolean {
+// // Handle action bar item clicks here. The action bar will
+// // automatically handle clicks on the Home/Up button, so long
+// // as you specify a parent activity in AndroidManifest.xml.
+// return when (item.itemId) {
+// R.id.action_settings -> true
+// else -> super.onOptionsItemSelected(item)
+// }
+// }
+//
+// override fun onSupportNavigateUp(): Boolean {
+// val navController = findNavController(R.id.nav_host_fragment_content_main)
+// return navController.navigateUp(appBarConfiguration)
+// || super.onSupportNavigateUp()
+// }
+
+ override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) {
+ startActivityForResult(intent, requestCode)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ fileChooser.onActivityResult(requestCode, resultCode, data)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
index 37cebe65..fa584a95 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/PermissionsActivity.kt
@@ -16,9 +16,10 @@ open class PermissionsActivity : BaseActivity() {
val permissionList = mutableListOf()
if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU) {
//文件读写
- permissionList.add(Permission.READ_MEDIA_IMAGES)
- permissionList.add(Permission.READ_MEDIA_AUDIO)
- permissionList.add(Permission.READ_MEDIA_VIDEO)
+// permissionList.add(Permission.READ_MEDIA_IMAGES)
+// permissionList.add(Permission.READ_MEDIA_AUDIO)
+// permissionList.add(Permission.READ_MEDIA_VIDEO)
+ permissionList.add(Permission.MANAGE_EXTERNAL_STORAGE)
} else {
//文件读写
permissionList.add(Permission.WRITE_EXTERNAL_STORAGE)
diff --git a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
index ad02cc83..26072081 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/activity/login/LoginViewModel.kt
@@ -10,9 +10,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.navinfo.omqs.Constant
import com.navinfo.omqs.bean.LoginUserBean
+import io.realm.Realm
+import io.realm.RealmConfiguration
import kotlinx.coroutines.*
import okio.IOException
import java.io.File
+import java.math.BigInteger
enum class LoginStatus {
/**
@@ -60,6 +63,17 @@ class LoginViewModel(
loginUser.value = LoginUserBean(username = "admin", password = "123456")
}
+ private fun initRealm() {
+ val password = "password".encodeToByteArray().copyInto(ByteArray(64))
+ // 1110000011000010111001101110011011101110110111101110010011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ Log.d("", "密码是: ${BigInteger(1, password).toString(2).padStart(64, '0')}")
+ val config = RealmConfiguration.Builder()
+ .directory(File(Constant.DATA_PATH))
+ .name("HDData")
+// .encryptionKey(password)
+ .build()
+ Constant.realm = Realm.getInstance(config)
+ }
/**
* 处理注册按钮
@@ -100,6 +114,8 @@ class LoginViewModel(
try {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_INIT)
createRootFolder(context)
+ // 初始化Realm
+ initRealm()
} catch (e: IOException) {
loginStatus.postValue(LoginStatus.LOGIN_STATUS_FOLDER_FAILURE)
}
@@ -122,6 +138,12 @@ class LoginViewModel(
val file = File(Constant.MAP_PATH)
if (!file.exists()) {
file.mkdirs()
+ Constant.DATA_PATH = Constant.ROOT_PATH + "/data/"
+ with(File(Constant.MAP_PATH)) {
+ if(!this.exists()) this.mkdirs()
+ }
+ with(File(Constant.DATA_PATH)) {
+ if(!this.exists()) this.mkdirs()
}
}
}
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
index b9b9992d..37d7f1dd 100644
--- a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterFragment.kt
@@ -1,22 +1,35 @@
package com.navinfo.omqs.ui.fragment.personalcenter
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.activity.viewModels
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.get
import androidx.navigation.fragment.findNavController
+import com.blankj.utilcode.util.UriUtils
+import com.github.k1rakishou.fsaf.FileChooser
+import com.github.k1rakishou.fsaf.callback.FSAFActivityCallbacks
+import com.github.k1rakishou.fsaf.callback.FileChooserCallback
+import com.google.android.material.snackbar.Snackbar
import com.navinfo.omqs.R
import com.navinfo.omqs.databinding.FragmentPersonalCenterBinding
/**
* 个人中心
*/
-class PersonalCenterFragment : Fragment() {
+class PersonalCenterFragment : Fragment(), FSAFActivityCallbacks {
private var _binding: FragmentPersonalCenterBinding? = null
private val binding get() = _binding!!
+ private val fileChooser by lazy { FileChooser(requireContext()) }
+ private val viewModel by lazy { viewModels().value }
override fun onCreateView(
@@ -25,7 +38,6 @@ class PersonalCenterFragment : Fragment() {
): View {
_binding = FragmentPersonalCenterBinding.inflate(inflater, container, false)
return binding.root
-
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -35,13 +47,37 @@ class PersonalCenterFragment : Fragment() {
when (it.itemId) {
R.id.personal_center_menu_offline_map ->
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
+ R.id.personal_center_menu_import_data -> {
+ // 用户选中导入数据,打开文件选择器,用户选择导入的数据文件目录
+ fileChooser.openChooseFileDialog(object: FileChooserCallback() {
+ override fun onCancel(reason: String) {
+ }
+
+ override fun onResult(uri: Uri) {
+ val file = UriUtils.uri2File(uri)
+ // 开始导入数据
+ viewModel.importOmdbData(file)
+ }
+ })
+ }
}
true
}
+
+ fileChooser.setCallbacks(this@PersonalCenterFragment)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
+
+ override fun fsafStartActivityForResult(intent: Intent, requestCode: Int) {
+ startActivityForResult(intent, requestCode)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ fileChooser.onActivityResult(requestCode, resultCode, data)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt
new file mode 100644
index 00000000..a0217e7d
--- /dev/null
+++ b/app/src/main/java/com/navinfo/omqs/ui/fragment/personalcenter/PersonalCenterViewModel.kt
@@ -0,0 +1,16 @@
+package com.navinfo.omqs.ui.fragment.personalcenter
+
+import androidx.lifecycle.ViewModel
+import java.io.File
+
+class PersonalCenterViewModel: ViewModel() {
+ fun importOmdbData(omdbFile: File) {
+ // 检查File是否为sqlite数据库
+ if (omdbFile == null || omdbFile.exists()) {
+ throw Exception("文件不存在")
+ }
+ if (!omdbFile.name.endsWith(".sqlite") and !omdbFile.name.endsWith("db")) {
+ throw Exception("文件不存在")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_import_export_24.xml b/app/src/main/res/drawable/ic_baseline_import_export_24.xml
new file mode 100644
index 00000000..0c6ecd72
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_import_export_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_map_test.xml b/app/src/main/res/layout/activity_map_test.xml
new file mode 100644
index 00000000..0d477f41
--- /dev/null
+++ b/app/src/main/res/layout/activity_map_test.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/personal_center_menu.xml b/app/src/main/res/menu/personal_center_menu.xml
index e66e3705..4e3502df 100644
--- a/app/src/main/res/menu/personal_center_menu.xml
+++ b/app/src/main/res/menu/personal_center_menu.xml
@@ -11,9 +11,9 @@
android:icon="@drawable/baseline_map_24"
android:title="离线地图" />
+ android:id="@+id/personal_center_menu_import_data"
+ android:icon="@drawable/ic_baseline_import_export_24"
+ android:title="导入数据" />