Android笔记(四)
1. 数据存储
1.1 文件存储
数据存储到文件中:
Context提供的openFileOutput()方法
- 第一个参数:文件名,不指定路径,默认存储到
/data/data/<package name>/files/ 下 - 第二个参数:文件操作模式,可选值MODE_PRIVATE(默认,覆盖)和MODE_APPEND(追加)
- 返回值:FileOutputStream对象
fun save(inputText: String) {
try {
val output = openFileOutput("data", Context.MODE_PRIVATE)
val writer = BufferedWriter(OutputStreamWriter(output))
writer.use {
it.write(inputText)
}
} catch(e: IOException) {
e.printStackTrace()
}
}
Kotlin小知识——use函数,保证Lambda表达式中代码全部执行完毕之后自动将外层的流关闭,不用再使用finally语句手动关闭流
从文件中读取数据:
Context提供的openFileInput()方法
- 参数:文件名
- 返回值:FileInputStream对象
fun load(): String {
val content = StringBuilder()
try {
val input = openFileInput("data")
val reader = BufferedReader(InputStreamReader(input))
reader.use {
reader.forEachLine() {
content.append(it)
}
}
} catch(e: IOException) {
e.printStackTrace()
}
return content.toString()
}
Kotlin小知识——forEachLine函数,将读到的每行内容回调到Lambda表达式中
1.2 SharedPreferences存储
键值对的方式存储,支持不同数据类型存储
将数据存储到SharedPreferences:
-
获取SharedPreferences对象
- Context类的getSharedPreferences()方法
- 第一个参数:文件名称,默认存储到
/data/data/<package name>/shared_prefs/ 下 - 第二个参数:操作模式,目前只有默认MODE_PRIVATE,其他均废弃
- Activity类的getPreferences()方法
- 参数:操作模式
- 自动以当前Activity类名为文件名
-
调用SharedPreferences对象的edit()方法获取SharedPreferences.Editor对象 -
向SharedPreferences.Editor对象添加数据,提供一系列putXxx()方法 -
调用Editor对象apply()方法提交添加的数据
button.setOnClickLietener {
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name", "Tom")
editor.putInt("age", 20)
editor.apply()
}
从SharedPreferences读取数据:
SharedPreferences对象提供一系列getXxx()方法
1.3 SQLite数据库存储
创建数据库:
提供SQLiteOpenHelper抽象类帮助创建和升级数据库
-
构造函数(一般选择参数少的即可)
- 第一个参数:Context
- 第二个参数:数据库名
- 第三个参数:允许在查询数据时返回一个自定义的Cursor,一般传入null即可
- 第四个参数:版本号
-
重写onCreate()方法:实现数据库创建 -
重写onUpgrade()方法:实现数据库升级 -
当版本号发生改变的时候会自动调用onUpgrade()函数实现数据库升级 -
getReadableDatabase()和geWritableDatabase()
- 创建或打开一个现有数据库,返回一个可对数据库操作对象SQLiteDataBase对象
- 不同在于数据不可写入时,前者返回只读方式打开数据库,后者出现异常
-
数据库文件一般存放在/data/data/<package name>/databases 下
增删改:
方法 | 参数描述 |
---|
SQLiteDataBase.insert() | 表名,null,ContentValues | SQLiteDataBase.update() | 表名,ContentValues,where,value | SQLiteDataBase.delete() | 表名,where,value |
ContentValues对象:提供一系列put()重载,用于向ContentValues中添加数据,将列名以及待添加值传入即可,例put("name", "The Da Vinci Code")
上述的where为约束条件,其中使用占位符? ,value是一个字符串数组,提供值替换占位符,例db.update("Book", contentValue, "name=?", arrayOf("The Da Vinci Code"))
查询数据:
SQLiteDataBase.query()返回一个Cursor对象
参数 | 对应SQL部分 |
---|
table | from table_name | colums | select column1, column2 | selection | where column = value | selectionArgs | value | gourpBy | group by column | having | having column = value | orderBy | order by column1, column2 |
Cursor常用方法:
- moveToFirst()
- moveToNext()
- getColumnIndex(列名):通过列名返回表中对应的位置索引
- getXxx(列索引):通过列的索引获取值,通常配合上个方法使用
- close()
SQLiteDataBase.execSQL(SQL语句,值)执行SQL语句,例db.execSQL(insert into Book(name, aythor, price) values(?, ?, ?), arrayOf("The Lost Symbol", "Dan Brown", "19.95"))
1.4 SQLite数据库实践
事务:
- SQLiteDataBase.beginTransaction():开启事务
- SQLiteDataBase.setTransactionSuccessful():设置事务执行成功
- SQLiteDataBase.endTransaction():结束事务
升级数据库:
按需求增加修改表,但又要保留之前数据
class MyDatabaseHelper(val context: Context, name: String, version: Int):
SQLiteOpenHelper(context, name, null, version) {
private val createBook = "create table Book ("+
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
}
override fun onupgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
}
}
新需求:增加一张Category表(category:类别)
class MyDatabaseHelper(val context: Context, name: String, version: Int):
SQLiteOpenHelper(context, name, null, version) {
private val createBook = "create table Book ("+
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text)"
private val createCategory = "create table Category(" +
"id integer primary key autoincremnet," +
"category_name text," +
"category_code integer)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
db.execSQL(createCategory)
}
override fun onupgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1)
db.execSQL(createCategory)
}
}
新需求:Book和Category建立关联,在Book表中添加一个category_id字段
class MyDatabaseHelper(val context: Context, name: String, version: Int):
SQLiteOpenHelper(context, name, null, version) {
private val createBook = "create table Book ("+
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text," +
"category_id integer)"
private val createCategory = "create table Category(" +
"id integer primary key autoincremnet," +
"category_name text," +
"category_code integer)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
db.execSQL(createCategory)
}
override fun onupgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1)
db.execSQL(createCategory)
if (oldVersion <= 2)
db.execSQL("alter table Book add column category_id integer")
}
}
Google退出了一个专门用于Android平台的数据库框架——Room,后面会进行学习
1.5 使用Kotlin高阶函数简化操作
简化SharedPreferences用法:
fun SharedPreferences.open(bolck: SharedPreferences.Editor.() -> Unit) {
val editor = edit()
editor.block()
editor.apply()
}
getSharedPreferences("data", Context.MODE_PRIVATE).open {
putString("name", "Tom")
putString("age", 20)
}
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("name", "Tom")
putString("age", 20)
}
简化ContentVaues用法:
Kotlin小知识——Kotlin中使用A to B会创建一个Pair对象,Kotlin中的mapOf(“Apple” to 1, “Pear” to 2) 可以快速创建一个map
fun cvOf(vararg pairs: Pair<String, Any>): ContentValues {
val cv = ContentValues()
for (pair in pairs) {
val key = pair.first
val value = pair.second
when(value) {
is Int -> cv.put(key, value)
is Long -> cv.put(key, value)
is Short -> cv.put(key, value)
is Float -> cv.put(key, value)
is Double -> cv.put(key, value)
is Boolean -> cv.put(key, value)
is String -> cv.put(key, value)
is Byte -> cv.put(key, value)
is ByteArray -> cv.put(key, value)
null -> cv.putNull(key)
}
}
return cv
}
val values = cvOf("name" to "Game of Thrones", "author" to "George Martin")
db.insert("Book", null, value)
KTX扩展库也提供了类似的contentValuesOf()方法
相关知识:
Java IO流 - CSDN博客
android studio使用database navigator查看数据库 - CSDN博客
Android中的Cursor到底是什么?如何理解Cursor的方法都在做什么事情?- CSDN博客
Cursor - Android Developers
Kotlin高阶函数的理解与使用 - 简书
2. 运行时权限
- 普通权限:只需要在AndroidManifest.xml文件中添加权限声明就可以
- 危险权限:必须用户手动授权(运行时权限处理)
程序运行时申请权限:
以Intent.ACTION_CALL权限为例
先在AndroidManifest.xml声明权限
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
makeCall.setOnClickListener {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CALL_PHONE), 1)
} else {
call()
}
}
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode,
permissions, grantResults)
when(requestCode) {
1 -> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED){
call()
} else {
Toast.makeText(...)
}
}
}
}
private fun call() {
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch(e: SecurityException) {
e.printStackTrace()
}
}
}
ContextCompat.checkSelfPermission()
- 第一个参数:Context
- 第二个参数:具体权限名
- 返回值和PackageManager.PERMISSION_GRANTED比较,相等则认为已授权
ActivityCompat.requestPermissions()
- 第一个参数:Activity实例
- 第二个参数:String数组,存放要申请的权限名
- 第三个参数:请求码
相关知识:
Android 危险权限列表 - CSDN博客
3. ContentProvider
主要用于在不同应用程序之间实现数据共享功能
3.1 Uri通用资源标志符
Uri代表要操作的数据,Android上可用每种资源都可以用Uri表示,URI包括URL
内容Uri: 给ContentProvider中的数据建立唯一标识符
- authority部分:用于对不同应用程序做区分,一般采用应用包名的方式进行命名,例
com.example.app.provider - path部分:对同一应用程序中不同表做区分,通常添加到authority后面
- 前面还要加上协议部分,标准格式:
content://com.example.app.provider/table1 - 可以在后面加上一个id,例
content://com.example.app.provider/table1/1 表示调用方期望访问的是com.example.app引用的table1表中id为1的数据
Uri.pares(内容URI)可以将内容Uri解析成Uri对象
3.2 ContentResolver基本用法
访问ContentProvider中共享数据一定要借助ContentResolver类(resolver:解析器),通过Context中的getContentResolver()获取实例
ContentResolver提供一系列insert()、update()、delete()、query(),与SQLite的方法只是在参数上有点差别(不接收表名,而是使用Uri参数)
query()方法参数 | 对应SQL部分 |
---|
uri | from table_name | projection | select column1, column2 | selection | where column = value | selectionArgs | value | sortOrder | order by column1, column2 |
其他方法与SQLite大致类似
读取联系人:
class MainActivity : AppCompatActivity() {
private val contactsList = ArrayList<String>()
private lateinit var adapter: ArrayAdapter<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList)
listview.adapter = adapter
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.READ_CONTACTS) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(android.Manifest.permission.READ_CONTACTS), 1)
} else {
readContacts()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
@SuppressLint("Range")
private fun readContacts() {
contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null) ?. apply {
while (moveToNext()) {
val name = getString(getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
))
val tel = getString(getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER
))
contactsList.add("$name\n$tel")
}
adapter.notifyDataSetChanged()
close()
}
}
}
3.3 创建自己的ContentProvider
新建一个类继承ContentProvider
class MyProvider : ContentProvider() {
override fun onCreate(): Boolean {
return false
}
override fun query(
p0: Uri,
p1: Array<out String>?,
p2: String?,
p3: Array<out String>?,
p4: String?
): Cursor? {
TODO("Not yet implemented")
}
override fun getType(p0: Uri): String? {
TODO("Not yet implemented")
}
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
TODO("Not yet implemented")
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?)
:Int{
TODO("Not yet implemented")
}
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
TODO("Not yet implemented")
}
}
使用UriMatcher类实现匹配内容URI功能,提供了addURI()方法,参数分别为authority、path、自定义代码,调用UriMatcher.match(uri)方法,返回值是能够匹配这个Uri对象所对应的自定义代码,利用这个代码可以判断调用方期望访问哪张表的数据
class MyProvider : ContentProvider() {
private val table1Dir = 0
private val table1Item = 1
private val table2Dir = 2
private val table2Item = 3
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
init {
uriMatcher.addURI("com.example.app.provider", "table1", table1Dir)
uriMatcher.addURI("com.example.app.provider", "table2", table2Dir)
uriMatcher.addURI("com.example.app.provider", "table1/#", table1Item)
uriMatcher.addURI("com.example.app.provider", "table2/#", table2Item)
}
override fun query(
p0: Uri,
p1: Array<out String>?,
p2: String?,
p3: Array<out String>?,
p4: String?
): Cursor? {
when (uriMatcher.match(p0)) {
table1Dir -> {
}
table2Dir -> {
}
table1Item -> {
}
table2Item -> {
}
}
...
}
...
}
一个内容URI所对应的MIME类型主要由3部分组成,有以下规定:
- 必须以vnd开头
- 内容URI以路径结尾,则后接android.cursor.dir/;以id结尾,则后接android.cursor.item/
- 最后接上
vnd.<authority>.<path> content://com.example.app.provider/table1 对应的MIME类型写成vnd.android.cursor.dir/vnd.com.example.app.provider.table1
override fun getType(p0: Uri) = when(uriMatcher.match(p0)) {
table1Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
table1Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
else -> null
}
完整的ContentProvider创建完成,其他应用可以使用ContentResolver访问我们程序中的数据
ContentProvider也是四大组件也需要向Activity一样在AndroidManifest.xml文件中注册
3.3 自定义ContentProvider实践
class DatabaseProvider : ContentProvider() {
private val bookDir = 0
private val bookItem = 1
private val categoryDir = 2
private val categoryItem = 3
private val authority = "com.example.databasetest.provider"
private val dbhelper: MyDatabaseHelper? = null
private val uriMatcher by lazy {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(authority, "book", bookDir)
matcher.addURI(authority, "book/#", bookItem)
matcher.addURI(authority, "category", categoryDir)
matcher.addURI(authority, "category/#", categoryItem)
matcher
}
override fun onCreate() = context?.let {
dbHelper = MyDatabaseHelper(it, "BookStore.db", 2)
true
}?: false
override fun query(
p0: Uri,
p1: Array<out String>?,
p2: String?,
p3: Array<out String>?,
p4: String?
) = dpHelper?.let {
val db = it.readableDatabase
val cursor = when (uriMatcher.match(p0)) {
bookDir -> db.query("Book", p1, p2, p3, null, null, p4)
bookItem -> {
val bookId = p0.pathSegments[1]
db.query("Book", p1, "id=?", arrayOf(bookId),
null, null, p4)
}
categoryDir -> db.query("Category", p1, p2, p3,
null, null, p4)
categoryItem -> {
val categoryId = p0.pathSegments[1]
db.query("Category", p1, "id=?", arrayOf(bookId),
null, null, p4)
}
else -> null
}
cursor
}
override fun getType(p0: Uri) = when(uriMatcher.match(p0)) {
bookDir ->
"vnd.android.cursor.dir/vnd.com.example.app.provider.book"
categoryDir ->
"vnd.android.cursor.dir/vnd.com.example.app.provider.category"
bookItem ->
"vnd.android.cursor.item/vnd.com.example.app.provider.book"
categoryItem ->
"vnd.android.cursor.item/vnd.com.example.app.provider.category"
else -> null
}
override fun insert(p0: Uri, p1: ContentValues?) = dpHelper?.let {
val db = it.writableDatabase
val uriReturn = when (uriMatcher.match(p0)) {
bookDir, bookItem -> {
val newBookId = dn.insert("Book", null, p1)
Uri.parse("content://$authority/book/$newBookId")
}
categoryDir, categoryItem -> {
val newCategoryId = dn.insert("Category", null, p1)
Uri.parse("content://$authority/category/$newCategoryId")
}
else -> null
}
uriReturn
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?)
= dpHelper?.let {
val db = it.writableDatabase
val deleteRows = when (uriMatcher.match(p0)) {
bookDir -> db.update("Book", p1, p2)
bookItem -> {
val bookId = p0.pathSegments[1]
db.update("Book", "id=?", arrayOf(bookId),)
}
categoryDir -> db.update("Category", p1, p2)
categoryItem -> {
val categoryId = p0.pathSegments[1]
db.update("Category","id=?", arrayOf(bookId),)
}
else -> 0
}
deleteRows
} ?: 0
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?) = dpHelper?.let {
val db = it.writableDatabase
val updateRows = when (uriMatcher.match(p0)) {
bookDir -> db.update("Book", p1, p2, p3)
bookItem -> {
val bookId = p0.pathSegments[1]
db.update("Book", p1, "id=?", arrayOf(bookId),)
}
categoryDir -> db.update("Category", p1, p2, p3)
categoryItem -> {
val categoryId = p0.pathSegments[1]
db.update("Category", p1, "id=?", arrayOf(bookId),)
}
else -> 0
}
updateRows
} ?: 0
}
相关知识:
Android中的Uri详解 - CSDN博客
ContentProvider详解 - 简书
内容提供程序基础知识 - Android 开发者
|