1. 前言
ContentProvider属于 Android的四大组件之一,即内容提供者。
主要作用是进程间 进行数据交互 & 共享 ,即跨进程通信
ContentProvider的底层是采用 Android中的Binder机制
2. ContentProvider的使用 2.1 使用方法
ContentProvider 是一种用于在应用之间共享数据的组件。实现一个 ContentProvider 时,必须重写以下方法:
onCreate()
这个方法在 ContentProvider 被创建时调用,通常用于初始化数据源。
query()
用于从数据源中查询数据。必须实现这个方法,以便其他应用可以检索数据。
insert()
用于向数据源中插入新数据。必须实现这个方法,以便其他应用可以添加数据。
update()
用于更新数据源中的现有数据。必须实现这个方法,以便其他应用可以修改数据。
delete()
用于从数据源中删除数据。必须实现这个方法,以便其他应用可以删除数据。
getType()
用于返回与特定 URI 相关联的数据类型。此方法不是必需的,但通常需要实现以提供 MIME 类型支持。
2.2 数据传递方式
ContentProvider主要以 表格的形式 组织数据
同时也支持文件数据,只是表格形式用得比较多
每个表格中包含多张表,每张表包含行 & 列,分别对应记录 & 字段
2.3 方法实现
2.4 示例 例如想要删除一个文件,可以重写其中的delete方法:
@Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 获取文件路径 String filePath = uri.getPath(); // 假设 URI 的路径是文件的绝对路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 File file = new File (filePath);int rowsDeleted = 0 ;if (file.exists()) { if (file.delete()) { rowsDeleted = 1 ; } } if (rowsDeleted > 0 ) { getContext().getContentResolver().notifyChange(uri, null ); } else { throw new IllegalArgumentException ("File not found: " + uri); } return rowsDeleted;}
3. ContentResolver的使用
3.1 使用方法 ContentProvider用于暴露数据, contentResolver用于操作数据。通过Context的getContentResolver()方法获取实例, 通过Uri对指定应用的表进行增删改查
3.2 实现方法 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
insert(Uri uri, ContentValues values)
向 ContentProvider 中插入新数据,并返回新插入数据的 URI。
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
更新 ContentProvider 中的数据,并返回受影响的行数。
delete(Uri uri, String selection, String[] selectionArgs)
从 ContentProvider 中删除数据,并返回受影响的行数。
getType(Uri uri)
返回与给定 URI 相关联的数据类型的 MIME 类型。
applyBatch(String authority, ArrayList<ContentProviderOperation> operations)
批量执行操作,这可以提高多个插入、更新或删除操作的效率。
notifyChange(Uri uri, ContentObserver observer)
registerContentObserver(ContentObserver observer)
unregisterContentObserver(ContentObserver observer)
3.3 示例 同样的,安装上述的删除文件的方法,我们只需要向其中的uri中传入方法调用即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import android.content.ContentResolver;import android.net.Uri;public void deleteFile (Uri fileUri) { ContentResolver contentResolver = getContentResolver(); int rowsDeleted = contentResolver.delete(fileUri, null , null ); if (rowsDeleted > 0 ) { System.out.println("File deleted successfully." ); } else { System.out.println("File deletion failed or file not found." ); } }
4. 完整示例 4.1 项目描述: 在ContentProvider项目中创建一个SQLite学生表,通过另一个ContentResolver程序项目对ContentProvider中的数据进行读写
4.2 ConstantData 为了在两个项目中统一使用到uri,表名等相同参数的元素,创建一个包含相同数据内容的ConstantData ,这个文件在两个项目中都需要申明使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.example.contentprovide;import android.content.UriMatcher;import android.net.Uri;public class ConstantData { public static final String AUTHORITY = "jcut.android.contentProvider" ; public static final String TABLE_NAME = "student" ; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME); public static final int STUDENTS = 1 ; public static final int STUDENT = 2 ; public static final String SID = "sid" ; public static final String STU_NO = "stu_no" ; public static final String NAME = "name" ; public static final String CLAZZ = "clazz" ; public static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher (UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, TABLE_NAME, STUDENTS); uriMatcher.addURI(AUTHORITY, TABLE_NAME + "/#" , STUDENT); } }
4.3 ContentProvider 在数据提供类中,我们根据上面可知需要完成对数据的CRUD方式,在此之前,我们需要先将学生表(StudentDBHelper)创建:
4.3.1 StudentDBHelper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.example.contentprovide;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;public class StudentDBHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "student_db" ; private static final String TABLE_NAME = "student" ; private static final int DATABASE_VERSION = 2 ; public StudentDBHelper (Context context) { super (context, DATABASE_NAME, null , DATABASE_VERSION); } @Override public void onCreate (SQLiteDatabase db) { String strSQL = "create table " + TABLE_NAME + "(sid integer primary key autoincrement ," + "stu_no varchar(100),name varchar( 100)," + " clazz varchar(100))" ; db.execSQL(strSQL); String sql = "insert into " + TABLE_NAME + " values(null, '202001', '李富贵', '计算机1班')" ; db.execSQL(sql); } @Override public void onUpgrade (SQLiteDatabase sqLiteDatabase, int i, int i1) { } }
4.3.2 StudentDao 在ContentProvider中,我们查询的方法返回的数据需要修改,以达到我们想要输出的格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.example.contentprovide;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;public class StudentDao { private SQLiteDatabase db; public SQLiteDatabase getDB () { return db; } public StudentDao (SQLiteOpenHelper dbHelper) { db = dbHelper.getWritableDatabase(); } public void execQuery (StringBuffer resultSB, final String strSQL) { try { Cursor cursor = db.rawQuery(strSQL, null ); cursor.moveToFirst(); resultSB.delete(0 , resultSB.length()); while (!cursor.isAfterLast()) { StringBuffer sb = new StringBuffer (" " ); resultSB.append(cursor.getInt(0 )).append("\t\t\t\t" ); resultSB.append(cursor.getString(1 )).append("\t\t\t\t" ); resultSB.append(cursor.getString(2 )).append("\t\t\t\t" ); resultSB.append(cursor.getString(3 )).append("\n" ); cursor.moveToNext(); } } catch (RuntimeException e) { e.printStackTrace(); } } }
4.3.3 StudentContentProvider 这个类需要实现ContentProvider,因为ContentResolver6也是基于这个文件中重写的方法进行操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.example.contentprovide;import android.content.ContentProvider;import android.content.ContentValues;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.net.Uri;import androidx.annotation.NonNull;import androidx.annotation.Nullable;public class StudentContentProvider extends ContentProvider { private StudentDBHelper dbOpenHelper = null ; @Override public boolean onCreate () { dbOpenHelper = new StudentDBHelper (this .getContext()); return true ; } @Nullable @Override public Cursor query (@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); switch (ConstantData.uriMatcher.match(uri)) { case ConstantData.STUDENTS: return db.query(ConstantData.TABLE_NAME, projection, selection, selectionArgs, null , null , sortOrder); default : throw new IllegalArgumentException ("Unknown URl " + uri); } } @Nullable @Override public String getType (@NonNull Uri uri) { return null ; } @Nullable @Override public Uri insert (Uri uri, ContentValues values) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); switch (ConstantData.uriMatcher.match(uri)) { case ConstantData.STUDENTS: db.insert(ConstantData.TABLE_NAME, null , values); return null ; default : throw new IllegalArgumentException ("Unknown URl " + uri); } } @Override public int delete (Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); int count = 0 ; switch (ConstantData.uriMatcher.match(uri)) { case ConstantData.STUDENTS: count = db.delete(ConstantData.TABLE_NAME, selection, selectionArgs); break ; default : throw new IllegalArgumentException ("Unknown URl " + uri); } return count; } @Override public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); switch (ConstantData.uriMatcher.match(uri)) { case ConstantData.STUDENTS: return db.update(ConstantData.TABLE_NAME, values, selection, selectionArgs); default : throw new IllegalArgumentException ("Unknown URl " + uri); } } }
至此,ContentProvider的数据暴露方已经编写完成,Main中只需要对其中的数据进行查询和调用即可
1 2 3 4 5 private void refreshData () { String sql = "select * from student" ; studentDao.execQuery(resultSB, sql); dataTV.setText(resultSB.toString()); }
4.3.4 服务声明 由于需要对项目中的数据端口暴露,就需要添加相应的权限声明:其中的authorities是在ConstantData中声明需要暴露出的端口号。
1 2 3 4 5 <provider android:authorities="jcut.android.contentProvider" android:name=".StudentContentProvider" android:exported="true" android:enabled="true"/>
4.4 ContentResolver 对于数据发送方需要进行的操作就简单了,只需将需要的操作提交到暴露出的端口号即可
4.4.1 数据传递 由于传递的数据比较简单,这里就不使用面向对象的方法操作来将对象和方法封装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 package com.example.contentresolver;import android.content.ContentResolver;import android.content.ContentValues;import android.database.Cursor;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.Toast;import androidx.activity.EdgeToEdge;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity { Button insert, delete, query; EditText txtStuNo, txtName, txtClazz; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); EdgeToEdge.enable(this ); setContentView(R.layout.activity_main); findViews(); insert.setOnClickListener(new InsertListener ()); query.setOnClickListener(new QueryListener ()); delete.setOnClickListener(new DeleteListener ()); } private void findViews () { insert = findViewById(R.id.insert); delete = findViewById(R.id.delete); query = findViewById(R.id.query); txtStuNo = findViewById(R.id.pdStuNo); txtName = findViewById(R.id.pdName); txtClazz = findViewById(R.id.pdClazz); } private class InsertListener implements View .OnClickListener { @Override public void onClick (View view) { String stuNo = txtStuNo.getText().toString().trim(); String name = txtName.getText().toString().trim(); String clazz = txtClazz.getText().toString().trim(); if (stuNo.isEmpty() || name.isEmpty() || clazz.isEmpty()) { Toast.makeText(getApplicationContext(), "请填写完整信息" , Toast.LENGTH_SHORT).show(); return ; } ContentValues cv = new ContentValues (); cv.put(ConstantData.STU_NO, stuNo); cv.put(ConstantData.NAME, name); cv.put(ConstantData.CLAZZ, clazz); ContentResolver cr = getContentResolver(); cr.insert(ConstantData.CONTENT_URI, cv); txtStuNo.setText("" ); txtName.setText("" ); txtClazz.setText("" ); Toast.makeText(MainActivity.this , "插入成功" , Toast.LENGTH_SHORT).show(); } } class QueryListener implements View .OnClickListener { @Override public void onClick (View view) { String stuNo = txtStuNo.getText().toString().trim(); String name = txtName.getText().toString().trim(); String clazz = txtClazz.getText().toString().trim(); txtStuNo.setText("" ); txtName.setText("" ); txtClazz.setText("" ); StringBuilder condition = new StringBuilder ("1 = 1" ); if (!stuNo.isEmpty()) { condition.append(" AND stu_no LIKE '%" ).append(stuNo).append("%'" ); } if (!name.isEmpty()) { condition.append(" AND name LIKE '%" ).append(name).append("%'" ); } if (!clazz.isEmpty()) { condition.append(" AND clazz LIKE '%" ).append(clazz).append("%'" ); } ContentResolver cr = getContentResolver(); Cursor cursor = cr.query(ConstantData.CONTENT_URI, null , condition.toString(), null , null ); if (cursor != null ) { StringBuilder sb = new StringBuilder (); while (cursor.moveToNext()) { sb.append("\n" ) .append(cursor.getInt(0 )).append("/" ) .append(cursor.getString(1 )).append("/" ) .append(cursor.getString(2 )).append("/" ) .append(cursor.getString(3 )); } cursor.close(); Toast.makeText(MainActivity.this , sb.toString(), Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this , "没有找到数据" , Toast.LENGTH_SHORT).show(); } } } class DeleteListener implements View .OnClickListener { @Override public void onClick (View view) { String stuNo = txtStuNo.getText().toString().trim(); txtStuNo.setText("" ); txtName.setText("" ); txtClazz.setText("" ); if (!stuNo.isEmpty()) { String condition = "stu_no = " + stuNo; ContentResolver cr = getContentResolver(); int count = cr.delete(ConstantData.CONTENT_URI, condition, null ); Toast.makeText(MainActivity.this , count + "条数据删除成功" , Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this , "输入要删除的学号" , Toast.LENGTH_SHORT).show(); } } } }
5. 注意事项 在ContentResolver中不需要对权限进行声明,只需要将需要传递的数据像网络请求一样发送到对应的端口号就行。
在Android Studio自带的虚拟机中无法达到同时运行两个项目程序,这里推荐使用真机环境进项调试和安装。