安卓速通(其实是期末复习~)(第六章+第七章)
ZealSinger 发布于 阅读:103 期末复习
我们知道之前说过Android提供了五种数据存储的方式,我们已经说了文件存储,SharedPreferences和SQLite三种,这三种都是单个应用程序内部的数据持久化操作(其实严格来讲也能被别的程序读取,但是会需要考虑到文件的权限设计,一般从安全角度而言是不允许的)
所以,为了能实现应用程序之间的数据操作,例如支付宝转账的时候需要知道收款人的电话号码,可能就需要系统通讯录提供信息给支付宝,Android提供了ContentProvider内容提供者的组件来实现上述需求,ContentProvider不仅是五大数据存储方式之一,也是四大组件之一,其功能是在不同的程序之间实现数据共享
ContentProvider在不同应用程序之间充当数据共享的标准API,如果想访问ContentProvider里面的数据就需要借助ContentReslover类进行对应的操作,该类对象可以通过Context.getContentReslover()获取,其流程如下
数据模型
ContentProvider内部会以简单的数据库表格形式存储数据,每个数据对应的会有一个_ID的唯一标识
Uri
别的程序通过Uri来联系ContentResolver,其结构如下
通过Uri.pase(uri)就可以获取到对应的Uri对象,有了Uri对象就可以在别的程序中进行访问
需要注意的是,如果需要进行对增删改查的操作,需要将Uri注册到UriMatcher类上,Uri注册到UriMatcher上的时候还会需要一个自定义Code,是一个自定义的匹配成功的响应码,主要的用途是用于多样化的匹配,从而进行多样化的处理
上述内容可能比较难理解,可以参考代码理解,代码效果和源码都贴上,这个案例代码也是在原本checkBox多选代码的基础上进行的修改的
// MainActivity代码
class MainActivity : ComponentActivity() {
private val TAG = "MainActivityLifecycle"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate called")
enableEdgeToEdge()
setContent {
KtAndroisTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
private val likeList:MutableSet<String> = mutableSetOf()
private fun handler(buttonView: View,isChecked: Boolean){
var checkBox = buttonView as CheckBox
var boxContent = checkBox.text.toString()
var hobbyText = this.findViewById<TextView>(R.id.hobbyText)
if(isChecked){ // 为true则说明被选中 加入到likeList中
likeList.add(boxContent)
hobbyText.setText("你选择的是:${likeList}")
if("篮球" == boxContent){
}else if("羽毛球"==boxContent){
val values = ContentValues()
values.put(MyContentProvider.NAME,"ZealSinger")
values.put(MyContentProvider.GRADE,"2022")
val values1 = ContentValues()
values1.put(MyContentProvider.NAME,"张三")
values1.put(MyContentProvider.GRADE,"2022")
val values2 = ContentValues()
values2.put(MyContentProvider.NAME,"李四")
values2.put(MyContentProvider.GRADE,"2022")
contentResolver.insert(MyContentProvider.CONTENT_URI,values)
contentResolver.insert(MyContentProvider.CONTENT_URI,values1)
contentResolver.insert(MyContentProvider.CONTENT_URI,values2)
val c = contentResolver.query(
MyContentProvider.CONTENT_URI,
arrayOf(MyContentProvider._ID, MyContentProvider.NAME, MyContentProvider.GRADE),
null, null, null
)
if (c != null) {
if (c.moveToFirst()) {
do{
// 获取列索引
val idIndex = c.getColumnIndex(MyContentProvider._ID)
val nameIndex = c.getColumnIndex(MyContentProvider.NAME)
val gradeIndex = c.getColumnIndex(MyContentProvider.GRADE)
// 提取数据
val id = c.getInt(idIndex)
val name = c.getString(nameIndex)
val grade = c.getString(gradeIndex)
// 输出日志
Log.d(
"MyContentProvider",
"ID=$id, NAME=$name, GRADE=$grade"
)
} while (c.moveToNext());
}
}
c?.close()
}else{
contentResolver.delete(
MyContentProvider.CONTENT_URI, MyContentProvider.NAME+"=?",
arrayOf("张三")
)
val c = contentResolver.query(
MyContentProvider.CONTENT_URI,
arrayOf(MyContentProvider._ID, MyContentProvider.NAME, MyContentProvider.GRADE),
null, null, null
)
if (c != null) {
if (c.moveToFirst()) {
do {
// 获取列索引
val idIndex = c.getColumnIndex(MyContentProvider._ID)
val nameIndex = c.getColumnIndex(MyContentProvider.NAME)
val gradeIndex = c.getColumnIndex(MyContentProvider.GRADE)
// 提取数据
val id = c.getInt(idIndex)
val name = c.getString(nameIndex)
val grade = c.getString(gradeIndex)
// 输出日志
Log.d(
"MyContentProvider",
"ID=$id, NAME=$name, GRADE=$grade"
)
} while (c.moveToNext());
}
}
c?.close()
}
}else{
likeList.remove(boxContent)
hobbyText.setText("你选择的是:${likeList}")
}
}
private val checkedChangeListener = CompoundButton.OnCheckedChangeListener{
buttonView, isChecked -> handler(buttonView,isChecked) // buttoView就是对应的每个checkBox isChecked就是该checkBox是否被选
}
fun getListener(): CompoundButton. OnCheckedChangeListener{
return checkedChangeListener
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
AndroidView(
factory = { context ->
// 加载 XML 布局
val view = View.inflate(context, R.layout.relativelayouttext, null)
val activity = context as MainActivity
// createRelativeLayout(context)
view.findViewById<CheckBox>(R.id.lq).setOnCheckedChangeListener(activity.getListener())
view.findViewById<CheckBox>(R.id.ymq).setOnCheckedChangeListener(activity.getListener())
view.findViewById<CheckBox>(R.id.ppq).setOnCheckedChangeListener(activity.getListener())
view
},
modifier = Modifier.padding(20.dp)
)
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
KtAndroisTheme {
Greeting("Android")
}
}
// 内容提供者的代码
/**
* 自定义 ContentProvider,用于管理学生数据的数据库操作。
* 功能包括:数据库创建、增删改查(CRUD)、URI 路由匹配。
*
* 关键点:
* 1. 使用 `UriMatcher` 匹配不同操作类型的 URI。
* 2. 通过 `SQLiteOpenHelper` 管理数据库版本和表结构。
* 3. 实现 ContentProvider 的生命周期方法(onCreate, insert, query 等)。
*/
class MyContentProvider : ContentProvider() {
companion object {
const val PROVIDER_NAME = "com.zeal.provider"
val CONTENT_URI: Uri = Uri.parse("content://$PROVIDER_NAME/students")
const val _ID = "_id"
const val NAME = "name"
const val GRADE = "grade"
private var STUDENTS_PROJECTION_MAP: HashMap<String, String>? = null
const val STUDENTS = 1
const val STUDENT_ID = 2
const val DATABASE_NAME = "College"
const val STUDENTS_TABLE_NAME = "students"
const val DATABASE_VERSION = 1
const val TAG = "MyContentProvider"
val CREATE_DB_TABLE = """
CREATE TABLE $STUDENTS_TABLE_NAME (
$_ID INTEGER PRIMARY KEY AUTOINCREMENT,
$NAME TEXT NOT NULL,
$GRADE TEXT NOT NULL
);
""".trimIndent()
// URI 匹配规则
val uriMatcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(PROVIDER_NAME, "students", STUDENTS) // 匹配所有学生
addURI(PROVIDER_NAME, "students/#", STUDENT_ID) // 匹配单个学生 #会自动被匹配
}
}
private lateinit var db: SQLiteDatabase
// 数据库辅助类 用于操作SQLite
private class DatabaseHelper(context: Context) : SQLiteOpenHelper(
context,
DATABASE_NAME,
null,
DATABASE_VERSION
) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_DB_TABLE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $STUDENTS_TABLE_NAME")
onCreate(db)
}
}
override fun onCreate(): Boolean {
val context = context ?: return false
val dbHelper = DatabaseHelper(context) // 初始化数据库操作
db = dbHelper.writableDatabase
Log.d(TAG,"SQLite数据库初始化创建成功")
return db != null
}
override fun insert(uri: Uri, values: ContentValues?): Uri {
val rowId = db.insert(STUDENTS_TABLE_NAME, "", values)
if (rowId > 0) {
val _uri = ContentUris.withAppendedId(CONTENT_URI, rowId)
context?.contentResolver?.notifyChange(_uri, null)
return _uri
}
throw SQLException("Failed to add a record into $uri")
}
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor {
val qb = SQLiteQueryBuilder().apply {
tables = STUDENTS_TABLE_NAME
when (uriMatcher.match(uri)) {
STUDENTS -> projectionMap = STUDENTS_PROJECTION_MAP
STUDENT_ID -> appendWhere("$_ID = ${uri.pathSegments[1]}")
else -> throw IllegalArgumentException("Unknown URI $uri")
}
}
val orderBy = sortOrder ?: NAME
val cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy)
cursor.setNotificationUri(context?.contentResolver, uri)
return cursor
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
val count = when (uriMatcher.match(uri)) {
STUDENTS -> db.update(STUDENTS_TABLE_NAME, values, selection, selectionArgs)
STUDENT_ID -> {
val id = uri.pathSegments[1]
val where = "$_ID = $id" + if (!TextUtils.isEmpty(selection)) " AND ($selection)" else ""
db.update(STUDENTS_TABLE_NAME, values, where, selectionArgs)
}
else -> throw IllegalArgumentException("Unknown URI $uri")
}
context?.contentResolver?.notifyChange(uri, null)
return count
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val count = when (uriMatcher.match(uri)) {
STUDENTS -> db.delete(STUDENTS_TABLE_NAME, selection, selectionArgs)
STUDENT_ID -> {
val id = uri.pathSegments[1]
val where = "$_ID = $id" + if (!TextUtils.isEmpty(selection)) " AND ($selection)" else ""
db.delete(STUDENTS_TABLE_NAME, where, selectionArgs)
}
else -> throw IllegalArgumentException("Unknown URI $uri")
}
context?.contentResolver?.notifyChange(uri, null)
return count
}
override fun getType(uri: Uri): String = when (uriMatcher.match(uri)) {
STUDENTS -> "vnd.android.cursor.dir/vnd.example.students"
STUDENT_ID -> "vnd.android.cursor.item/vnd.example.students"
else -> throw IllegalArgumentException("Unsupported URI: $uri")
}
}
内容观察者
我们上面知道,实际上是利用ContentResolver通过Uri查询ContentProvider,这个过程是主动的,即我们手动主动的调用ContentResolver去查询,如果要求实时性很高,就需要频繁的调用。为了解决这个问题,Android提供了内容观察者ContentObserver,可以用于观察指定Uri的数据变化,当他检测到Uri代表的数据发生变化的时候就会触发其内部的onChange()方法,此时我们只需要在onChange方法中配置我们需要的逻辑就可以作到主动监听的作用
这里可以类比消息队列和消费者之间的消息通知机制
可以消费者一直轮询拉去消息,即类比没有ContentObserver;也可以当消息队列中一有消息就主动同之消费者,消费者只需要监听这个消息,这个就是有ContentObserver
内容观察者的创建类似内容提供者,创建一个类继承ContentnObserver,重写里面的onChange方法即可
public class MyObserver extends ContentObserver {
public MyObserver(Handler handler) {
super(handler);
}
//数据发生改变时做的事情
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
}
有了内容观察者之后,利用ContentResolver和Uri就可以把上面定义的内容观察者注册,自然也可以取消注册
public void registerObserver(){
//获取观察者对象
ContentResolver contentResolver = getContentResolver();
//获取uri 感兴趣的uri
Uri uri = Uri.parse("content://com.xiji.mydatabase.provider/user");
//注册内容观察者
contentResolver.registerContentObserver(uri, true, new MyObserver(new Handler()));
// 取消注册
ContentResolver contentResolver = getContentResolver();
contentResolver.unregisterContentObserver(new MyObserver(new Handler()));
}
广播机制
定义
广播是一种在各种组件之间传递消息的机制,例如电池过低就会发送一条提示广播
Android中的广播机制用于进程之间或者线程之间的通信,使用了观察者模式
观察者模式基于消息发布订阅模型,消息订阅者即广播接收者注册到注册的中心AMS上 ; 消息发送者即广播发送者通过Binder机制发送广播到AMS消息中心;消息中心发送消息给消息订阅者即广播接收者
其大致流程如下:
-
广播接收者通过Binder机制在处理中心AMS中进行注册
-
广播发送者通过Binder机制想AMS发送广播
-
AMS查找符合条件的广播接收者,会将广播发送到相应的消息循环队列中
-
程序执行消息循环的时候会获取到此广播,并且回调消息订阅者即广播接收者的onReceiver()方法进行相关的处理
针对于不同的广播类型和不同的广播接收者的注册方式,广播机制的实际实现上会有所不同,但是大致都是如上的过程。因为双方相当于是通过注册中心AMS作为中介进行传播,所以可以实现同一个APP内的同一组件内的消息通信 ; 同一个APP内的不同组件之间的消息通信 ; 同一个APP内的多进程的不同组件之间的通信 ; 不同APP的组件之间的通信 ; 特殊情况下实现Android和APP之间的通信
广播接收者
广播接收者的是指就是BroadcastReceiver类,自定义类继承该类且重写其onReceive()方法
public class MyReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
// TODO: This method is called when the BroadcastReceiver is receiving
// an Intent broadcast.
throw new UnsupportedOperationException("Not yet implemented");
}
}
而只是重写了不行,还需要将其注册到AMS上才能发会作用
注册广播接收者的方法分为动态注册和静态注册
-
动态注册:在MainActivity中通过代码的方式进行注册
主要借助的几个方法是:IntentFilter()定义过滤规则 ; registerReciver(广播接收者对象,过滤规则)注册广播接收者;unregisterReciver(广播接收者)
public class MainActivity extends AppCompatActivity {
private MyDivBroadcastReceiver mDivReceiver;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
//定义广播接收者
mDivReceiver = new MyDivBroadcastReceiver();
String action = "myAction";
//广播过滤
IntentFilter filter = new IntentFilter();
filter.addAction(action);
//注册
registerReceiver(mDivReceiver, filter);
}
protected void onDestroy() {
super.onDestroy();
//销毁注册
unregisterReceiver(mDivReceiver);
}
} -
静态注册
当我们通过Android 自带的快捷New创建广播接收者之后,广播接收者会自动被添加到AnndroidManifest.xml即总配置文件中
<receiver
android:name=".MyDivBroadcastReceiver"
android:enabled="true"
android:exported="true"></receiver>这里的三个属性name表示要注册的广播接收者 ; enabled表示广播是否被系统注册,true则说明系统会自己去注册广播接收者,不需要我们担心注册问题 ; 第三个参数exported表示可以接收当前程序之外的程序的广播信息(允许不同App之间的广播),这种情况下系统会帮我们注册,不需要我们显示调用registerReceiver方法进行注册
自定义广播与广播类型
所谓自定义广播,就是在当前程序发送一个广播信息,然后再目标接收方创建一个对应的广播接收者即可实现。
如下是一个简单的不完整的案例,大概能看出来就行。三个广播接收者都绑定过滤action,即接收action为“message”的广播信息,button就是广播发送者,buttonSendMessage.setOnClickListener就button按钮被点击的时候,触发逻辑,就会发送一个action为meaasge的,包含了内容为“myMessage”的intent作为消息发送出去,最后会三个接收者接收到
public class MainActivity extends AppCompatActivity {
private MyDivBroadcastReceiver mDivReceiver;
//定义三个接受者
private ReceiverMessage mReceiverMessage;
private ReceiverMessageTwo mReceiverMessageTwo;
private ReceiverMessageThree mReceiverMessageThree;
//发送事件
private Button buttonSendMessage;
//编辑消息
private EditText editTextMessage;
//订阅者初始化
private void initSubscriber() {
//定义广播接收者
String action = "message";
mReceiverMessage = new ReceiverMessage();
mReceiverMessageTwo = new ReceiverMessageTwo();
mReceiverMessageThree = new ReceiverMessageThree();
//为每个接受者设置过滤器
IntentFilter filter = new IntentFilter(action);
filter.addAction(action);
IntentFilter filterTwo = new IntentFilter(action);
filterTwo.addAction(action);
IntentFilter filterThree = new IntentFilter(action);
filterThree.addAction(action);
//注册广播接收者
registerReceiver(mReceiverMessage, filter);
registerReceiver(mReceiverMessageTwo, filterTwo);
registerReceiver(mReceiverMessageThree, filterThree);
//注册
.....
}
//控件监听初始化
private void initListener() {
buttonSendMessage = findViewById(R.id.buttonSendMessage);
editTextMessage = findViewById(R.id.editText);
buttonSendMessage.setOnClickListener(view -> {
//发送消息
String message = editTextMessage.getText().toString();
Intent intent = new Intent();
intent.setAction("message");
intent.putExtra("myMessage", message);
// 利用intent和sendBroadcast发送广播消息
// 这里是发送的有序广播 下面会讲
sendOrderedBroadcast(intent, null);
});
}
}
根据广播发送者的需求,分为两种广播类型:无序广播 和 有序广播
-
无序广播:完全异步执行,所有的订阅了这个action的广播接收者都会收到这个消息,但是收到的顺序和处理顺序都是不可控的,传播效率很高但是无法被拦截,使用的是sendBroadcast()方法
-
有序广播:按照广播接收者的声明的优先级进行先后广播。优先级高得接收者会率先收到广播消息并且只有当当前这个广播接收者完成处理之后才会发送到下一个广播接收者,采取严格的优先级顺序处理,可以被拦截,但是效率较慢且会被阻塞,使用的是sendOrderedBroadcast()方法
文章标题:安卓速通(其实是期末复习~)(第六章+第七章)
文章链接:https://zealsinger.xyz/?post=12
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自ZealSinger !
如果觉得文章对您有用,请随意打赏。
您的支持是我们继续创作的动力!

微信扫一扫

支付宝扫一扫