android基础02-广播、持久化、权限声明与动态获取、ContentProvider、Application单例类
广播
Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。
- 标准广播(normal broadcasts)
完全异步执行的广播,发出后所有 BroadcastReceiver 几乎同时受到,效率较高也无法截断。 - 有序广播(ordered broadcasts)
同步执行的广播,广播发出后 BroadcastReceiver 按照优先级一个一个接受。将广播截断后后面的就无法接收到了。
接收广播
注册BroadcastReceiver的方式一般有两种:在代码中注册和在AndroidManifest.xml中注册。
- 动态注册监听时间变化
必须在程序启动之后才能接收广播
class MainActivity : AppCompatActivity() { inner class TimeChangeReceiver : BroadcastReceiver() { // 重写了接收到广播时执行的方法 override fun onReceive(context: Context, intent: Intent) { Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show() } } lateinit var timeChangeReceiver: TimeChangeReceiver // 继承自 BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 指定系统时间变化的事件:android.intent.action.TIME_TICK // 查看完整的广播列表 val intentFilter = IntentFilter() intentFilter.addAction("android.intent.action.TIME_TICK") // 指定处理事件的动作 timeChangeReceiver = TimeChangeReceiver() registerReceiver(timeChangeReceiver, intentFilter) } override fun onDestroy() { super.onDestroy() // 记得销毁时取消注册 unregisterReceiver(timeChangeReceiver) } }
- 静态注册实现开机启动
BroadcastReceiver中是不允许开启线程的,长时间执行不结束会导致程序异常
在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。 https://developer.android.google.cn/guide/components/broadcast-exceptions.html
一个特殊的广播为android.intent.action.BOOT_COMPLETED
用静态注册的方式来接收开机广播,然后在onReceive()方法里执行相应的逻辑,这样就可以实现开机启动的功能了。
class BootCompleteReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show() } }
// 生成BroadcastReceiver时会在AndroidManifest.xml 中自动注册,需要修改 <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> // 敏感操作要进行权限声明 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:allowBackup="true" // .... tools:targetApi="31"> <receiver android:name=".BootCompleteReceiver" android:enabled="true" android:exported="true"> // 指定监听的 action <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
发送自定义广播
先自定义一个广播接收器:
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "received in MyBroadcastReceiver",
Toast.LENGTH_SHORT).show()
}
}
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
// 接收自定义广播
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
- 标准广播
button.setOnClickListener { val intent = Intent("com.example.broadcasttest.MY_BROADCAST") // packageName是getPackageName()的语法糖写法,用于获取当前应用程序的包名。 intent.setPackage(packageName) // 默认发出是隐式广播,为了静态注册的广播能接收到必须指定发送给哪个程序 sendBroadcast(intent) }
- 有序广播
在发送广播时使用另一个重载的方法
button.setOnClickListener { val intent = Intent("com.example.broadcasttest.MY_BROADCAST") intent.setPackage(packageName) // 第二个参数是一个与权限相关的字符串,传入null即可 sendOrderedBroadcast(intent, null) }
注册其他的 BroadcastReceiver,并指定权重
<receiver android:name=".MyBroadcastReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="100"> // 权重为100,越大越有限 <action android:name="com.example.broadcasttest.MY_BROADCAST"/> </intent-filter> </receiver>
class MyBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show() // 拦截广播,后面的无法接收到 abortBroadcast() } }
持久化
当前 Activity Context 中自带
内部存储
写文件时默认存储到 /data/data/<package name>/files/
路径下,默认两种模式 MODE_PRIVATE(默认操作,覆盖原文件) 和 MODE_APPEND
Context类中提供的 openFileInput() 和 openFileOutput() 方法
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inputText = load()
if (inputText.isNotEmpty()) {
editText.setText(inputText)
editText.setSelection(inputText.length)
Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show()
}
}
override fun onDestroy() {
super.onDestroy()
val inputText = editText.text.toString()
save(inputText)
}
// 读文件
private 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()
}
// 写文件
private fun save(inputText: String) {
try {
val output = openFileOutput("data", Context.MODE_PRIVATE) // 返回 FileOutputStream对象
val writer = BufferedWriter(OutputStreamWriter(output)) // 构建出一个BufferedWriter对象
writer.use { // 内置函数,保证代码执行玩后自动关闭流
it.write(inputText)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}
外部存储
私有外部存储
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File file = new File(getExternalFilesDir(null), "myexternalfile.txt");
try {
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write("私有外部存储文件内容".getBytes());
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File file = new File(getExternalFilesDir(null), "myexternalfile.txt");
try {
FileInputStream inputStream = new FileInputStream(file);
byte[] buffer = new byte[1024];
int length;
StringBuilder stringBuilder = new StringBuilder();
while ((length = inputStream.read(buffer)) > 0) {
stringBuilder.append(new String(buffer, 0, length));
}
inputStream.close();
String fileContent = stringBuilder.toString();
// 处理文件内容
} catch (IOException e) {
e.printStackTrace();
}
}
公共外部存储
// 公共外部存储 读无需申请权限,写需要申请
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "mypublicfile.txt");
try {
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write("公共外部存储文件内容".getBytes());
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_EXTERNAL_STORAGE);
}
SharedPreferences存储
用键值对的方式来存储数据
两种方法获取 SharedPreferences 对象:
- Context类中的getSharedPreferences()方法
参数1 指定文件名称,存放在 /data/data/<package_name>/shared_prefs/ 目录
参数2 指定操作模式,现在只有 MODE_PRIVATE 一种,与传入0相同,表示可以读写 - Activity类中的getPreferences()方法
将Activity名作为文件名,只接收操作模式
saveButton.setOnClickListener {
// 1. 获取一个 SharedPreferences.Editor 对象
val editor = getSharedPreferences("fileName", Context.MODE_PRIVATE).edit()
// 2. 添加数据
editor.putString("name", "Tom")
editor.putInt("age", 28)
editor.putBoolean("married", false)
// 3. 提交
editor.apply()
}
restoreButton.setOnClickListener {
// 1. 获取对象
val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
// 2. 读数据
val name = prefs.getString("name", "")
val age = prefs.getInt("age", 0)
val married = prefs.getBoolean("married", false)
Log.d("MainActivity", "name is $name")
Log.d("MainActivity", "age is $age")
Log.d("MainActivity", "married is $married")
}
SQLite数据库存储
抽象类 SQLiteOpenHelper 有两个抽象方法 onCreate()
onUpgrade()
,getReadableDatabase()
getWritableDatabase()
用于打开或新建数据库
一般使用参数较少的构造方法:
- 参数1,Context
- 参数2,数据库名
- 参数3,自定义Cursor,一般传null
- 参数4,当前数据库的版本号,可用于对数据库进行升级操作
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)"
// 调用 val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1) 时创建数据库
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}
// 调用 val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2) 时更新数据库
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("drop table if exists Book")
onCreate(db)
}
}
数据库放在 /data/data/<package_name>/databases/
目录下
- 增
insert() 方法,有4个参数:
参数1 表名
参数2 在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般直接传null
参数3 一个 ContentValues 对象,它提供了一系列的put()方法重载,用于添加数据
addData.setOnClickListener { val db = dbHelper.writableDatabase val values1 = ContentValues().apply { // 开始组装第一条数据 put("name", "The Da Vinci Code") put("author", "Dan Brown") put("pages", 454) put("price", 16.96) } db.insert("Book", null, values1) // 插入第一条数据 val values2 = ContentValues().apply { // 开始组装第二条数据 put("name", "The Lost Symbol") put("author", "Dan Brown") put("pages", 510) put("price", 19.95) } db.insert("Book", null, values2) // 插入第二条数据 }
- 改
update(表名, 存储数据的ContentValues对象, 约束更新某几行 默认更新全部)
updateData.setOnClickListener { val db = dbHelper.writableDatabase val values = ContentValues() values.put("price", 10.99) db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code")) }
- 删
deleteData.setOnClickListener { val db = dbHelper.writableDatabase db.delete("Book", "pages > ?", arrayOf("500")) }
- 查
queryData.setOnClickListener { val db = dbHelper.writableDatabase // 查询Book表中所有的数据 val cursor = db.query( "Book", // 1. 表名 null, // 2. 指定查哪几列,默认所有 null, // 3. 指定行 null, // 4. 搭配3 null, // 5. group by null, // 6. having null) // 7. orderBy if (cursor.moveToFirst()) { do { // 遍历Cursor对象,取出数据并打印 val name = cursor.getString(cursor.getColumnIndex("name")) val author = cursor.getString(cursor.getColumnIndex("author")) val pages = cursor.getInt(cursor.getColumnIndex("pages")) val price = cursor.getDouble(cursor.getColumnIndex("price")) Log.d("MainActivity", "book name is $name") Log.d("MainActivity", "book author is $author") Log.d("MainActivity", "book pages is $pages") Log.d("MainActivity", "book price is $price") } while (cursor.moveToNext()) } cursor.close() }
- 使用sql
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96") ) db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", arrayOf("The Lost Symbol", "Dan Brown", "510", "19.95") ) db.execSQL("update Book set price = ? where name = ?", arrayOf("10.99", "The Da Vinci Code")) db.execSQL("delete from Book where pages > ?", arrayOf("500")) val cursor = db.rawQuery("select * from Book", null)
事务
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
replaceData.setOnClickListener {
val db = dbHelper.writableDatabase
db.beginTransaction() // 开启事务
try {
db.delete("Book", null, null)
if (true) { // 手动抛出一个异常,让事务失败,用于观察
throw NullPointerException()
}
val values = ContentValues().apply {
put("name", "Game of Thrones")
put("author", "George Martin")
put("pages", 720)
put("price", 20.85)
}
db.insert("Book", null, values)
db.setTransactionSuccessful() // 事务已经执行成功
} catch (e: Exception) {
e.printStackTrace()
} finally {
db.endTransaction() // 结束事务
}
}
数据库版本升级
新安装的软件使用onCreate方法,没有其他注意点。升级软件使用onUpgrade,要对每一种旧版本进行检测
- 版本1 只有一个book
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) } }
- 版本2 有 book 和 category 两张表
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) } }
- 版本3 为 book 表添加字段
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") } }
Android运行时权限
- 普通权限
不会影响用户,系统会自动帮我们进行授权,如接收开启启动权限 - 危险权限
可能影响用户(较少,android10共11组30个)
原则上用户同意某个权限后同组权限也被同意,但安卓系统随时可能跳转权限组 - 特殊权限(少见)
静态申请权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
</manifest>
动态申请权限
Intent.ACTION_DIAL 是打开拨号界面,是普通权限
Intent.ACTION_CALL 是拨打电话
// 拨打电话消耗电话费,是危险操作。这里拨打前申请权限
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall.setOnClickListener {
// 1. 检查是否被授权
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// 2. 申请权限
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 -> {
// 3. 判断是否获得权限
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun call() {
// 拨打电话
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
java版:高版本Android 权限不仅要在xml中声明,还要在代码中动态获取:
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.widget.Toast;
public class MainActivity extends Activity {
private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permissionCheck!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_EXTERNAL_STORAGE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_WRITE_EXTERNAL_STORAGE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "权限已授予,可以进行外部存储操作。", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "权限被拒绝,无法进行外部存储操作。", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
ContentProvider
跨程序共享数据
使用现有的ContentProvider
一个应用程序通过ContentProvider对其数据提供了外部访问接口,那么任何其他的应用程序都可以对这部分数据进行访问。如通信录、短信、媒体库等提供的接口
使用 ContentResolver 类,它提供了 insert update delete query 等方法,不接收表名而是接收内容URI。内容URI由 content:// 协议声明、包名.provider、用于区分表的路径:content://com.example.app.provider/path1
//查
val uri = Uri.parse("content://com.example.app.provider/table1")
val cursor = contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder)
while (cursor.moveToNext()) {
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()
//增
val values = contentValuesOf("column1" to "text", "column2" to 1)
contentResolver.insert(uri, values)
//改
val values = contentValuesOf("column1" to "")
contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", "1"))
//删
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
/**
* 申请权限访问通讯录并将访问的数据放入 ListView 控件
* 要在 AndroidManifest.xml 中声明权限:<uses-permission android:name="android.permission.READ_CONTACTS" />
*/
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)
contactsView.adapter = adapter
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.READ_CONTACTS), 1)
} else {
readContacts()
}
}
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) {
readContacts()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun readContacts() {
// 查询联系人数据
contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null)?.apply {
while (moveToNext()) {
// 获取联系人姓名
val displayName = getString(getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
// 获取联系人手机号
val number = getString(getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER))
contactsList.add("$displayName\n$number")
}
adapter.notifyDataSetChanged()
close()
}
}
}
创建自己的ContentProvider
URI 路径:
content://com.example.app.provider/table1 // 指定表
content://com.example.app.provider/table1/1 // 指定表中id为1的数据
content://com.example.app.provider/* // *匹配任意长度字符串
content://com.example.app.provider/table1/# // #匹配任意长度数字
andoid的 MIME 规定:
1. 以 vnd 开头
2. URI以路径结尾接上 android.cursor.dir/
以id结尾则接上 android.cursor.item/
3. 最后接上 vnd.<authority>.<path>
content://com.example.app.provider/table1 对应的MIME为 vnd.android.cursor.dir/vnd.com.example.app.provider.table1
content://com.example.app.provider/table1/1 对应的MIME为 vnd.android.cursor.item/vnd.com.example.app.provider.table1
创建ContentProvider,创建java或kt文件,并在 AndroidManifest.xml 中注册:
通过继承抽象类:
class MyProvider : ContentProvider() {
// 初始化 ContentProvider 时调用,完成对数据库的创建和升级等操作,返回true成功,false失败
override fun onCreate(): Boolean {
return false
}
override fun query(uri: Uri, // 指定表
projection: Array<String>?, // 指定列
selection: String?, // 指定行
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
return null
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
return null
}
// 返回受影响的行数
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}
// 返回被删除的行数
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}
// 根据 URI 返回 MIME 类型
override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
table1Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
table1Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
else -> null
}
}
通过ContentProvider对外提供操作Sqlite的接口
ContentProvider 包装 Sqlite 操作
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 var dbHelper: MyDatabaseHelper? = null
private val uriMatcher by later {
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(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
) = dbHelper?.let {
// 查询数据
val db = it.readableDatabase
val cursor = when (uriMatcher.match(uri)) {
bookDir -> db.query("Book", projection, selection, selectionArgs, null, null, sortOrder)
bookItem -> {
val bookId = uri.pathSegments[1]
db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
}
categoryDir -> db.query("Category", projection, selection, selectionArgs, null, null, sortOrder)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.query("Category", projection, "id = ?", arrayOf(categoryId), null, null, sortOrder)
}
else -> null
}
cursor
}
override fun insert(uri: Uri, values: ContentValues?) = dbHelper?.let {
// 添加数据
val db = it.writableDatabase
val uriReturn = when (uriMatcher.match(uri)) {
bookDir, bookItem -> {
val newBookId = db.insert("Book", null, values)
Uri.parse("content://$authority/book/$newBookId")
}
categoryDir, categoryItem -> {
val newCategoryId = db.insert("Category", null, values)
Uri.parse("content://$authority/category/$newCategoryId")
}
else -> null
}
uriReturn
}
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?) =
dbHelper?.let {
// 更新数据
val db = it.writableDatabase
val updatedRows = when (uriMatcher.match(uri)) {
bookDir -> db.update("Book", values, selection, selectionArgs)
bookItem -> {
val bookId = uri.pathSegments[1]
db.update("Book", values, "id = ?", arrayOf(bookId))
}
categoryDir -> db.update("Category", values, selection, selectionArgs)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.update("Category", values, "id = ?", arrayOf(categoryId))
}
else -> 0
}
updatedRows
} ?: 0
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = dbHelper?.let {
// 删除数据
val db = it.writableDatabase
val deletedRows = when (uriMatcher.match(uri)) {
bookDir -> db.delete("Book", selection, selectionArgs)
bookItem -> {
val bookId = uri.pathSegments[1]
db.delete("Book", "id = ?", arrayOf(bookId))
}
categoryDir -> db.delete("Category", selection, selectionArgs)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.delete("Category", "id = ?", arrayOf(categoryId))
}
else -> 0
}
deletedRows
} ?: 0
override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
bookDir -> "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book"
bookItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book"
categoryDir -> "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category"
categoryItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category"
else -> null
}
}
Application 单例类
- 每个
Android App
运行时,会首先自动创建Application
类并实例化Application
对象,且只有一个 - 不同的组件(如
Activity、Service
)都可获得Application
对象且都是同一个对象 Application
对象的生命周期是整个程序中最长的,即等于Android App
的生命周期
使用方式:
-
新建Application子类
public class CarsonApplication extends Application { private static final String VALUE = "Carson"; // 初始化全局变量 @Override public void onCreate() { super.onCreate(); VALUE = 1; } }
-
在
Manifest.xml
文件中<application>
标签里进行配置<application android:name=".CarsonApplication"> # 此处自定义Application子类的名字 = CarsonApplication </application>
- 在组件(Activity、Service)中使用
private CarsonApplicaiton app; // 只需要调用Activity.getApplication() 或Context.getApplicationContext()就可以获得一个Application对象 app = (CarsonApplication) getApplication(); // 然后再得到相应的成员变量 或方法 即可 app.exitApp();