Android开发--Lesson06--内容提供者共享数据

内容提供者概述

ContentProvider 是 Android 中用于在不同应用程序之间共享数据的一个重要组件。它封装了数据,并提供了一组标准的接口来访问和修改这些数据

核心概念

  1. 数据抽象与隔离:

    • ContentProvider 提供了一种标准化的方式来访问应用中的数据,无论这些数据是如何存储的(如 SQLite 数据库、文件系统等)。
    • 它允许不同的应用程序之间安全地共享数据,而不需要直接访问对方的应用程序空间。
  2. URI (Uniform Resource Identifier):

    • 每个 ContentProvider 都有一个唯一的 URI 来标识它。这个 URI 通常以 content:// 开头。
    • 例如,content://com.example.app.provider/table1 可能指向一个名为 table1 的表。
  3. 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-----

 

posted @ 2025-04-21 11:13  回忆也交给时间  阅读(23)  评论(0)    收藏  举报