一 内容提供者
背景:
之前提到过内容提供者就是在访问数据的时候 那么它因何诞生
我们之前的数据访问像SQLite之类访问都是在当前应用程序访问
那则么能行 而其他应用程序之间的访问 就需要这一组件的帮助
画的有点粗糙
但大致就是这样工作的
B通过ContentResolver类访问A中ContentProvider暴露(共享)的数据
A通过ContentResolver类将结果返回给B
1.ContentResolver类 就是中介
2.ContentProvider实质上就是内容提供者
3.通过 uri 唯一标识 建立连接
那什么是uri呢?
这个就是Android中内容访问的 uri固定格式
content:// 包名 路径
当然当你访问Android中自有的东西例如通讯录人家的uri都封装好了直接用就行
那看看程序中的内容提供者是怎样的
是的就是一个java继承类
但是你会发现清单文件中出现了这个:
那光创建了 咋访问数据呢并且显示出来呢
简简单单三步走: 1.通过parse()方法解析uri Uri uri=Uri.parse(你定义的或者自带的 uri标识); 2.query()方法查询数据(需要先创建ContentResolver对象) ContentResolver resolver=context.getContentResolver(); Cursor cursor=resolver.query(5个参数) 3.while循环遍历游标 while(cursor.moveToNext()){ cursor对应的getInt()或者getString()方法获取然后输出 } cursor.close()关闭释放
query中的五个参数
1.uri
2.查询内容
3.查询条件
4.配合参数
5.升序或者降序排序
这里还有多学一招 就是多个数据时 要使用到UriMatcher类
再来看看内容提供者的一个例子 读取手机通讯录
这里就不写layout文件美化了 也不录入数据
就直接在手机中添加联系人 然后log打印出来 主要看功能
后面一个综合例子 会写的
package com.example.four_content; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract; import android.util.Log; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; public class ContactActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getPermissions(); } //关于危险权限 如申请(位置 日历 照相机 联系人 存储卡 传感器 麦克风 电话 短信)权限 //不仅需要在清单文件中静态申请权限 还要在代码中动态申请权限 //获取手机通讯录数据之前 需要申请读取手机通讯录的权限 所以需要在getPermissions()方法中申请获取权限 // 并重写onRequestPermissionsResult()方法 读取权限是否申请成功信息 String[] permissions; public void getPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //版本是否大于6.0 Android 6.0开始,应用需要在运行时请求一些敏感权限 //初始化permissions数组,仅包含一个权限:读取联系人权限 permissions = new String[]{"android.permission.READ_CONTACTS"}; //创建一个ArrayList来存储尚未被授予的权限 ArrayList<String> list = new ArrayList<>(); //遍历permissions数组,并检查每个权限是否已经被授予 for (int i = 0; i < permissions.length; i++) { if (ActivityCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) { list.add(permissions[i]); //如果list中有未被授予的权限,则请求这些权限。1是请求代码,用于在onRequestPermissionsResult方法中识别这个特定的权限请求 if (list.size() > 0) { ActivityCompat.requestPermissions(this, list.toArray(new String[list.size()]), 1); } else { setData(); } } else { setData(); } } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); //检查请求代码是否为1,即之前getPermissions()方法中请求的权限 if (requestCode == 1) { for (int i = 0; i < permissions.length; i++) { //如果请求的是读取联系人权限,并且该权限已经被授予,则显示“读取成功”的Toast消息 if (permissions[i].equals("android.permission.READ_CONTACTS") && grantResults[i] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "读取成功", Toast.LENGTH_SHORT).show(); setData(); } else { Toast.makeText(this, "读取失败", Toast.LENGTH_SHORT).show(); } } } } public void setData(){ ContentResolver contentResolver = getContentResolver(); Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null); while (cursor.moveToNext()){ @SuppressLint("Range") String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); @SuppressLint("Range") int anInt = Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))); if (anInt>0){ Cursor cursor1 = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + id, null, null); while (cursor1.moveToNext()){ @SuppressLint("Range") String num = cursor1.getString(cursor1.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); Log.i("高远", "setData: "+num); } cursor1.close(); } } cursor.close(); } } 清单文件中加入:静态访问权限 <uses-permission android:name="android.permission.READ_CONTACTS" />
log打印
注意注意 上述代码是有误的
参考:https://www.cnblogs.com/tiancaige/p/10051563.html
就是遍历那里 contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id, null, null); selectiong "=" 查询条件要加引号
二 内容观察者
背景:
那当访问其他应用程序数据时候 共享的数据是否发生变化了
我们就要使用到内容观察者ContentObserver来通过指定的uri来观察
1.当B操作A中数据发生变化时候 先ContentProvider调用ContentResolver的notifyChange()方法 发送消息中心数据变化的信息
2.C(内容观察者)得知消息中心数据变化时 触发ContentObserver的onChange()方法
关于内容观察者如何使用:
1.创建内容观察者 实质上是写一个继承ContentObserver的java类
2.注册内容观察者 通过ContentResolver的registerContentObserver()方法注册
3.取消注册 在onDestroy()方法中通过ContentResolver的unregisterContentObserver()方法
来看一个综合例子 监测数据的变化
三个玩意:
1.A内容提供者
2.B操作A
3.C内容观察者
跳过layout布局了 大致样式:
1.创建一个SQLite数据库
package com.example.contents; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class Myhelper extends SQLiteOpenHelper { //构造方法,调用该方法创建一个person.db数据库 public Myhelper(Context context) { super(context, "gao.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { //创建该数据库的同时新建一个info表,表中有_id,name这两个字段 db.execSQL("create table info(_id integer primary key autoincrement, name varchar(20))"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
2.编写A ContentProvider内容提供者
package com.example.contents; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; public class MyContentProvider extends ContentProvider { //定义一个uri路径的匹配器,如果路径匹配不成功返回-1 private static UriMatcher mUriMatcher = new UriMatcher(-1); private static final int SUCCESS = 1; //匹配路径成功时的返回码 private Myhelper helper; //数据库操作类的对象 //添加路径匹配器的规则 static { mUriMatcher.addURI("com.example.contents", "info", SUCCESS); } @Override public boolean onCreate() { //当内容提供者被创建时调用 helper = new Myhelper(getContext()); return false; } /** * 查询数据操作 */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //匹配查询的Uri路径 int code = mUriMatcher.match(uri); if (code == SUCCESS) { SQLiteDatabase db = helper.getReadableDatabase(); return db.query("info", projection, selection, selectionArgs, null, null, sortOrder); } else { throw new IllegalArgumentException("路径不正确,无法查询数据!"); } } /** * 添加数据操作 */ @Override public Uri insert(Uri uri, ContentValues values) { int code = mUriMatcher.match(uri); if (code == SUCCESS) { SQLiteDatabase db = helper.getReadableDatabase(); long rowId = db.insert("info", null, values); if (rowId > 0) { Uri insertedUri = ContentUris.withAppendedId(uri, rowId); //提示数据库的内容变化了 getContext().getContentResolver().notifyChange(insertedUri, null); return insertedUri; } db.close(); return uri; } else { throw new IllegalArgumentException("路径不正确,无法插入数据!"); } } /** * 删除数据操作 */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int code = mUriMatcher.match(uri); if (code == SUCCESS) { SQLiteDatabase db = helper.getWritableDatabase(); int count = db.delete("info", selection, selectionArgs); //提示数据库的内容变化了 if (count > 0) { getContext().getContentResolver().notifyChange(uri, null); } db.close(); return count; } else { throw new IllegalArgumentException("路径不正确,无法随便删除数据!"); } } /** * 更新数据操作 */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int code = mUriMatcher.match(uri); if (code == SUCCESS) { SQLiteDatabase db = helper.getWritableDatabase(); int count = db.update("info", values, selection, selectionArgs); //提示数据库的内容变化了 if (count > 0) { getContext().getContentResolver().notifyChange(uri, null); } db.close(); return count; } else { throw new IllegalArgumentException("路径不正确,无法更新数据!"); } } //返回MIME类型的数据 文档文件字节流格式 (例如windows中的.txt .jpg文件) @Override public String getType(Uri uri) { return null; } }
3.编写B 操作A中共享的数据
package com.example.contents; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; public class BmakeA_Activity extends AppCompatActivity implements View.OnClickListener { private ContentResolver resolver; private Uri uri; private ContentValues values; private Button btnInsert; private Button btnUpdate; private Button btnDelete; private Button btnSelect; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_contents); initView(); //初始化界面 createDB(); //创建数据库 } private void initView() { btnInsert = findViewById(R.id.btn_insert); btnUpdate = findViewById(R.id.btn_update); btnDelete = findViewById(R.id.btn_delete); btnSelect = findViewById(R.id.btn_select); btnInsert.setOnClickListener(this); btnUpdate.setOnClickListener(this); btnDelete.setOnClickListener(this); btnSelect.setOnClickListener(this); } private void createDB() { //创建数据库并向info表中添加3条数据 Myhelper helper = new Myhelper(this); SQLiteDatabase db = helper.getWritableDatabase(); for (int i = 0; i < 3; i++) { ContentValues values = new ContentValues(); values.put("name", "itcast" + i); db.insert("info", null, values); } db.close(); } @Override public void onClick(View v) { //得到一个内容提供者的解析对象 resolver = getContentResolver(); //获取一个Uri路径 uri = Uri.parse("content://com.example.contents/info"); //新建一个ContentValues对象,该对象以key-values的形式来添加数据到数据库表中 values = new ContentValues(); if (v.getId() == R.id.btn_insert) { Random random = new Random(); values.put("name", "add_itcast" + random.nextInt(10)); Uri newuri = resolver.insert(uri, values); Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show(); Log.i("数据库应用", "添加"); } if (v.getId() == R.id.btn_delete) { //返回删除数据的条目数 int deleteCount = resolver.delete(uri, "name=?", new String[]{"itcast0"}); Toast.makeText(this, "成功删除了" + deleteCount + "行", Toast.LENGTH_SHORT).show(); Log.i("数据库应用", "删除"); } if (v.getId() == R.id.btn_select) { List<Map<String, String>> data = new ArrayList<Map<String, String>>(); //返回查询结果,是一个指向结果集的游标 Cursor cursor = resolver.query(uri, new String[]{"_id", "name"}, null, null, null); //遍历结果集中的数据,将每一条遍历的结果存储在一个List的集合中 while (cursor.moveToNext()) { Map<String, String> map = new HashMap<String, String>(); map.put("_id", cursor.getString(0)); map.put("name", cursor.getString(1)); data.add(map); } //关闭游标,释放资源 cursor.close(); Log.i("数据库应用", "查询结果:" + data.toString()); } if (v.getId() == R.id.btn_update) { //将数据库info表中name为itcast1的这条记录更改为name是update_itcast values.put("name", "update_itcast"); int updateCount = resolver.update(uri, values, "name=?", new String[]{"itcast1"}); Toast.makeText(this, "成功更新了" + updateCount + "行", Toast.LENGTH_SHORT).show(); Log.i("数据库应用", "更新"); } } }
4.创建C(内容观察者)程序
注意是一个新的项目: package com.example.contentobserver; import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 该uri路径指向数据库应用中的数据库info表 Uri uri = Uri.parse("content://com.example.contents/info"); //注册内容观察者,参数uri指向要监测的数据库info表, //参数true定义了监测的范围,最后一个参数是一个内容观察者对象 getContentResolver().registerContentObserver(uri, true, new MyObserver(new Handler())); } private class MyObserver extends ContentObserver { public MyObserver(Handler handler) {//handler 是一个消息处理器。 super(handler); } @Override //当info表中的数据发生变化时则执行该方法 public void onChange(boolean selfChange) { Log.i("监测数据变化", "有人动了你的数据库!"); super.onChange(selfChange); } } @Override protected void onDestroy() { super.onDestroy(); //取消注册内容观察者 getContentResolver().unregisterContentObserver(new MyObserver( new Handler())); } }
5.测试
先启动操作数据和内容提供者 数据情况如图
然后启动内容观察者
当点击事件后
内容提供者:
数据改变:
当数据变化时 都会调用 此方法 然后发送给数据中心数据变化的消息
getContext().getContentResolver().notifyChange(uri, null);
ok了至此结束
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?