Android开发--Lesson06--内容提供者共享数据
内容提供者概述
ContentProvider
是 Android 中用于在不同应用程序之间共享数据的一个重要组件。它封装了数据,并提供了一组标准的接口来访问和修改这些数据
核心概念
-
数据抽象与隔离:
ContentProvider
提供了一种标准化的方式来访问应用中的数据,无论这些数据是如何存储的(如 SQLite 数据库、文件系统等)。- 它允许不同的应用程序之间安全地共享数据,而不需要直接访问对方的应用程序空间。
-
URI (Uniform Resource Identifier):
- 每个
ContentProvider
都有一个唯一的 URI 来标识它。这个 URI 通常以content://
开头。 - 例如,
content://com.example.app.provider/table1
可能指向一个名为table1
的表。
- 每个
-
CRUD 操作:
-
ContentProvider
支持四种基本的数据操作:创建(Create)、读取(Retrieve)、更新(Update)和删除(Delete),即 CRUD 操作。 -
这些操作通过
ContentResolver
类的方法实现,包括insert()
、query()
、update()
和delete()
-
ContentProvide提供了一套API用来访问其暴露的数据,但是是不能直接直接拿取这些数据的,它需要ContentProvider作为中间件来进行协调,包括增删改查。
创建内容共享者步骤
1.在已有的Android的项目下右键包名,然后new --》 other --》 contentProvider
2.填写URI,class Name可以默认
3.点击finish就可以完成contentProvider的创建
ContentProvider
如果想要使用ContentProvider,需要继承这个抽象类,然后实现其的方法,需要暴露那些方法就实现那些方法:
public class MyContentProvider extends ContentProvider { //操作SQLite数据库的类 private MyCreateDB myCreateDB; //UriMatcher.NO_MATCH 是一个常量值,表示“未匹配”。它的值为 -1 private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //常数 private static final int QUERY = 1; private static final int QUERYONE = 2; static { /** * "com.cqust.product.myContentProvider" 是 authority,必须与 ContentProvider 的声明一致。 * "message" 是路径,表示查询所有数据。 * "message/#" 是带 ID 的路径,表示查询单条数据(# 表示数字占位符)。 * QUERY_ALL 和 QUERY_ONE 是自定义的整数常量,用于标识不同的 URI 类型 */ uriMatcher.addURI("com.cqust.product.myContentProvider","query",QUERY); uriMatcher.addURI("com.cqust.product.myContentProvider","query/#",QUERYONE); } public MyContentProvider() { } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // Implement this to handle requests to delete one or more rows. throw new UnsupportedOperationException("Not yet implemented"); } @Override public String getType(Uri uri) { // TODO: Implement this to handle requests for the MIME type of the data // at the given URI. throw new UnsupportedOperationException("Not yet implemented"); } @Override public Uri insert(Uri uri, ContentValues values) { // TODO: Implement this to handle requests to insert a new row. throw new UnsupportedOperationException("Not yet implemented"); } @Override public boolean onCreate() { //初始化 myCreateDB = new MyCreateDB(getContext()); return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //重写query方法 Cursor cursor = null; SQLiteDatabase rdb = myCreateDB.getReadableDatabase(); try { int match = uriMatcher.match(uri); System.out.println("math:"+match); switch (match){ case QUERY: //查询所有的数据 cursor = rdb.query("message",projection,selection,selectionArgs,null,null,selection); break; case QUERYONE: long id = ContentUris.parseId(uri); cursor = rdb.query("message",projection,"id=?",new String[]{String.valueOf(id)},null,null,selection); break; } }catch (Exception e){ System.out.println("query发生异常:"+e.getMessage()); } return cursor; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO: Implement this to handle requests to update one or more rows. throw new UnsupportedOperationException("Not yet implemented"); } }
在如上代码中,需要查询SQLite数据库,故而操作数据库的建表操作都是上一节学过的,这里就直接copy代码:
public class MyCreateDB extends SQLiteOpenHelper { public MyCreateDB(@Nullable Context context) { super(context, "data.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { //创建数据库 db.execSQL("create table message(id Integer primary key autoincrement, name varchar(32),price Float)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
编写一个存储数据库的XML页面,两个写入框和一个按钮:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <EditText android:id="@+id/et1" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入用户名"/> <EditText android:id="@+id/et2" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入价格"/> <Button android:id="@+id/bt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击"/> </LinearLayout>
MainActity页面,用来操作数据库,包括存库:
public class MainActivity extends AppCompatActivity { @Override 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; }); EditText et1 = findViewById(R.id.et1); EditText et2 = findViewById(R.id.et2); Button btn = findViewById(R.id.bt); btn.setOnClickListener((v)->{ MyCreateDB db = new MyCreateDB(MainActivity.this); SQLiteDatabase rdb = db.getReadableDatabase(); ContentValues cv = new ContentValues(); cv.put("name",et1.getText().toString()); cv.put("price",Float.valueOf(et2.getText().toString())); rdb.insert("message",null,cv); rdb.close(); }); } }
访问其它内容分享者的信息者,是一个其它的项目:
public class MainActivity extends AppCompatActivity { // 在Android O及以上版本中启用EdgeToEdge @RequiresApi(api = Build.VERSION_CODES.O) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启用EdgeToEdge 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; }); // 获取按钮和编辑框 Button btn = findViewById(R.id.bt1); EditText et = findViewById(R.id.et1); // 设置按钮点击事件 btn.setOnClickListener(v->{ // 解析Uri Uri uri = Uri.parse("content://com.cqust.product.myContentProvider/query"); // 获取ContentResolver ContentResolver resolver = getContentResolver(); // 查询数据 Cursor cursor = null; try { cursor = resolver.query(uri, null, null, null, null); // 打印查询结果数量 System.out.println(cursor.getCount()); }catch (Exception e){ } // 创建消息列表 ArrayList<Message> list = new ArrayList<>(); // 如果查询结果不为空 if (cursor.getCount() != 0){ // 遍历查询结果 while (cursor.moveToNext()){ // 添加消息到列表 list.add(new Message(cursor.getInt(0),cursor.getString(1),cursor.getFloat(2))); } // 关闭游标 cursor.close(); } // 创建结果字符串 String result=""; // 遍历消息列表 for (int i = 0; i < list.size(); i++) { // 拼接结果字符串 result+="id:"+list.get(i).getId(); result+="--name:"+list.get(i).getName(); result+="--price:"+list.get(i).getPrice(); result+="\n"; } // 设置编辑框文本 et.setText(result); }); } }
对应的AML页面:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/bt1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="点击"/> <EditText android:id="@+id/et1" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="点击取值"/> </LinearLayout>
Mesage对象:
public class Message { private int id; private String name; private Float price; public Message() { } public Message(int id, String name, Float price) { this.id = id; this.name = name; this.price = price; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } @Override public String toString() { return "Message{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}'; } }
数据提供者项目
这是包含 ContentProvider
的项目,负责提供数据。它的主要职责包括:
- 在
AndroidManifest.xml
中声明ContentProvider
。 - 实现
ContentProvider
的方法(如query()
、insert()
等)。 - 提供数据源(例如 SQLite 数据库或其他存储方式)。
要求:
- 数据提供者项目必须已经安装到设备或模拟器上。
- 数据提供者项目的
ContentProvider
必须正确注册并运行。 - 如果
ContentProvider
使用了权限,确保调用方项目已声明并获取了相应的权限。
数据调用者项目
这是需要访问数据的项目,它通过 ContentResolver
调用 ContentProvider
提供的数据。它的主要职责包括:
- 构造正确的
Uri
。 - 使用
ContentResolver.query()
或其他方法与ContentProvider
交互。
要求:
- 数据调用者项目不需要直接启动数据提供者的
Activity
或其他组件。 - 只要数据提供者的
ContentProvider
已经注册并运行,调用者项目就可以通过ContentResolver
访问数据。
小结:简单讲就是数据共享者需要启动提供查询数据服务,数据调用者调用其实现的接口
内容观察者
ContentObserver(内容观察者) 是 Android 框架中的一个类,用于监听特定 Uri
所代表的数据的变化。当数据发生变化时,ContentObserver
会收到通知,从而允许应用程序对这些变化做出响应。它通常与内容提供者(ContentProvider
)配合使用,用于监控数据的变化,如联系人、日历事件或自定义内容提供者中的数据。
在在上面内容提供者的基础之上修改获取内容提供者的项目代码:
public class MainActivity extends AppCompatActivity { // 在Android O及以上版本中启用EdgeToEdge @RequiresApi(api = Build.VERSION_CODES.O) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 启用EdgeToEdge 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; }); //注册内容观察者 getContentResolver().registerContentObserver(Uri.parse("content://com.losstime.MyContentProvider/insert"),true,new MyObserver(new Handler())); getContentResolver().registerContentObserver(Uri.parse("content://com.losstime.MyContentProvider/delete"),true,new MyObserver(new Handler())); getContentResolver().registerContentObserver(Uri.parse("content://com.losstime.MyContentProvider/update"),true,new MyObserver(new Handler())); // 获取按钮和编辑框 Button btn = findViewById(R.id.bt1); TextView et = findViewById(R.id.et1); // 设置按钮点击事件 btn.setOnClickListener(v->{ // 解析Uri Uri uri = Uri.parse("content://com.losstime.MyContentProvider/query"); // 获取ContentResolver ContentResolver resolver = getContentResolver(); // 查询数据 Cursor cursor = null; try { cursor = resolver.query(uri, null, null, null, null); // 打印查询结果数量 System.out.println(cursor.getCount()); }catch (Exception e){ System.out.println("cursor.getCount()异常"); } // 创建消息列表 ArrayList<Information> list = new ArrayList<>(); // 如果查询结果不为空 if (cursor.getCount() != 0){ // 遍历查询结果 while (cursor.moveToNext()){ // 添加消息到列表 list.add(new Information(cursor.getString(0),cursor.getString(1))); } // 关闭游标 cursor.close(); } // 创建结果字符串 String result=""; // 遍历消息列表 for (int i = 0; i < list.size(); i++) { // 拼接结果字符串 result+="--name:"+list.get(i).getName(); result+="--phoneNumber:"+list.get(i).getPhonenumber(); result+="\n"; } // 设置编辑框文本 et.setText(result); }); // 获取按钮和两个输入框 Button bt2 = findViewById(R.id.bt2); EditText et2 = findViewById(R.id.et2); EditText et3 = findViewById(R.id.et3); // 为按钮添加点击事件监听器 bt2.setOnClickListener(v->{ // 创建Uri对象 Uri uri = Uri.parse("content://com.losstime.MyContentProvider/insert"); // 获取ContentResolver对象 ContentResolver resolver = getContentResolver(); // 创建ContentValues对象 ContentValues values = new ContentValues(); // 将输入框中的内容放入ContentValues对象中 values.put("name", et2.getText().toString()); values.put("phonenumber", et3.getText().toString()); // 调用ContentResolver的insert方法插入数据 resolver.insert(uri, values); // 弹出操作成功的提示 Toast.makeText(MainActivity.this,"操作成功",Toast.LENGTH_LONG).show(); }); // 添加删除监听器 this.addDeleteListener(); // 添加更新监听器 this.addUpdateListener(); } // 添加删除按钮的监听器 public void addDeleteListener(){ // 获取删除按钮和两个输入框 Button bt3 = findViewById(R.id.bt3); EditText et4 = findViewById(R.id.et4); EditText et5 = findViewById(R.id.et5); // 设置删除按钮的点击事件 bt3.setOnClickListener(v->{ // 构造删除的Uri Uri uri = Uri.parse("content://com.losstime.MyContentProvider/delete"); // 获取ContentResolver ContentResolver resolver = getContentResolver(); // 执行删除操作 resolver.delete(uri,"name=? and phonenumber = ?",new String[]{et4.getText().toString(),et5.getText().toString()}); // 弹出操作成功的提示 Toast.makeText(MainActivity.this,"操作成功",Toast.LENGTH_LONG).show(); }); } // 添加更新按钮的监听器 public void addUpdateListener(){ // 获取更新按钮和三个输入框 Button bt4 = findViewById(R.id.bt4); EditText et6 = findViewById(R.id.et6); EditText et7 = findViewById(R.id.et7); EditText et8 = findViewById(R.id.et8); // 创建ContentValues对象 ContentValues cv = new ContentValues(); // 设置更新按钮的点击事件 bt4.setOnClickListener(v->{ // 获取旧用户名 String et6Str = et6.getText().toString(); // 判断旧用户名是否为空 if (et6Str.isEmpty()){ // 弹出提示 Toast.makeText(MainActivity.this,"旧用户名不能为空",Toast.LENGTH_SHORT).show(); return; } // 获取新用户名和新电话号码 String et7Str = et7.getText().toString(); String et8Str = et8.getText().toString(); // 判断新用户名和新电话号码是否都为空 if (et7Str.isEmpty() && et8Str.isEmpty()){ // 弹出提示 Toast.makeText(MainActivity.this,"修改的电话和号码不能都为空",Toast.LENGTH_SHORT).show(); return; } // 根据新用户名和新电话号码是否为空,设置ContentValues if (et7Str.isEmpty()){ cv.put("phonenumber",et8Str); } if (et8Str.isEmpty()){ cv.put("name",et7Str); } if (!et7Str.isEmpty()&&!et8Str.isEmpty()){ cv.put("phonenumber",et8Str); cv.put("name",et7Str); } // 构造更新的Uri Uri uri = Uri.parse("content://com.losstime.MyContentProvider/update"); // 获取ContentResolver ContentResolver resolver = getContentResolver(); // 打印旧用户名、新用户名和新电话号码 System.out.println("et6:"+et6Str+"---et7:"+et7Str+"---et8:"+et8Str); // 执行更新操作 resolver.update(uri,cv,"name = ?",new String[]{et6Str}); // 弹出操作成功的提示 Toast.makeText(MainActivity.this,"操作成功",Toast.LENGTH_LONG).show(); }); } }
其中:
//注册内容观察者 getContentResolver().registerContentObserver(Uri.parse("content://com.losstime.MyContentProvider/insert"),true,new MyObserver(new Handler())); getContentResolver().registerContentObserver(Uri.parse("content://com.losstime.MyContentProvider/delete"),true,new MyObserver(new Handler())); getContentResolver().registerContentObserver(Uri.parse("content://com.losstime.MyContentProvider/update"),true,new MyObserver(new Handler()));
这段代码就是在进行内容提供者的观察,需要填写观测的地址,只有被注册时候内容观察者才能进行监听
内容观察者:
public class MyObserver extends ContentObserver { /** * Creates a content observer. * * @param handler The handler to run {@link #onChange} on, or null if none. */ public MyObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); Log.i("observer","--数据改变了--"); } }
当内容提供者变化之后,内容观察者监听到之后就会输出一段文本数据
需要注意的是,需要内容观察者收到数据变化的信息,需要在内容提供者中声明一下,使用notifyChange()方法,只有增删改需要提示,查询其实是没有数据变化的:
@Override //更新数据 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { //获取可读数据库 SQLiteDatabase rdb = myCreateDB.getReadableDatabase(); //更新数据 int msg = rdb.update("information", values, selection, selectionArgs); //关闭数据库 rdb.close(); //通知更新 getContext().getContentResolver().notifyChange(uri, null); return msg; }
上面这段代码摘录自内容提供者MyContentProvider
-----END-----