«

安卓速通(其实是期末复习~)(第六章+第七章)

ZealSinger 发布于 阅读:103 期末复习


内容提供者和内容观察者

我们知道之前说过Android提供了五种数据存储的方式,我们已经说了文件存储,SharedPreferences和SQLite三种,这三种都是单个应用程序内部的数据持久化操作(其实严格来讲也能被别的程序读取,但是会需要考虑到文件的权限设计,一般从安全角度而言是不允许的)

所以,为了能实现应用程序之间的数据操作,例如支付宝转账的时候需要知道收款人的电话号码,可能就需要系统通讯录提供信息给支付宝,Android提供了ContentProvider内容提供者的组件来实现上述需求,ContentProvider不仅是五大数据存储方式之一,也是四大组件之一,其功能是在不同的程序之间实现数据共享

ContentProvider在不同应用程序之间充当数据共享的标准API,如果想访问ContentProvider里面的数据就需要借助ContentReslover类进行对应的操作,该类对象可以通过Context.getContentReslover()获取,其流程如下

image-20250516093900122

数据模型

ContentProvider内部会以简单的数据库表格形式存储数据,每个数据对应的会有一个_ID的唯一标识

image-20250516151736205

Uri

别的程序通过Uri来联系ContentResolver,其结构如下

通过Uri.pase(uri)就可以获取到对应的Uri对象,有了Uri对象就可以在别的程序中进行访问

image-20250516152100211

需要注意的是,如果需要进行对增删改查的操作,需要将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);
  }
   
   //数据发生改变时做的事情
   @Override
   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消息中心;消息中心发送消息给消息订阅者即广播接收者

其大致流程如下:

  1. 广播接收者通过Binder机制在处理中心AMS中进行注册

  2. 广播发送者通过Binder机制想AMS发送广播

  3. AMS查找符合条件的广播接收者,会将广播发送到相应的消息循环队列中

  4. 程序执行消息循环的时候会获取到此广播,并且回调消息订阅者即广播接收者的onReceiver()方法进行相关的处理

针对于不同的广播类型和不同的广播接收者的注册方式,广播机制的实际实现上会有所不同,但是大致都是如上的过程。因为双方相当于是通过注册中心AMS作为中介进行传播,所以可以实现同一个APP内的同一组件内的消息通信 ; 同一个APP内的不同组件之间的消息通信 ; 同一个APP内的多进程的不同组件之间的通信 ; 不同APP的组件之间的通信 ; 特殊情况下实现Android和APP之间的通信

广播接收者

广播接收者的是指就是BroadcastReceiver类,自定义类继承该类且重写其onReceive()方法

public class MyReceiver extends BroadcastReceiver {

   @Override
   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上才能发会作用

注册广播接收者的方法分为动态注册和静态注册

自定义广播与广播类型

所谓自定义广播,就是在当前程序发送一个广播信息,然后再目标接收方创建一个对应的广播接收者即可实现。

如下是一个简单的不完整的案例,大概能看出来就行。三个广播接收者都绑定过滤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);
      });
  }

}

根据广播发送者的需求,分为两种广播类型:无序广播 和 有序广播

 

Android Kotlin 期末复习 编程