Android ContentProvider、FileProvider

ContentProvider 主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许—个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。不同于文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,ContentProvider 可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。

运行时权限

在程序运行时申请权限

AndroidManifest.xml 文件




    

    


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById

访问其他程序中的数据

ContentProvider 的基本用法

內容 URI 给 ContentProvider 中的数据建立了唯一标识符,它主要由两部分组成:authority 和 path。 authority 是用于对不同的应用程序做区分的,一般为了避免冲突,会采用应用包名的方式进行命名。比如某个应用的包名是 com.example.app,那么该应用对应的 authority 就可以命名为 com.example.app.provider。 path 则是用于对同一应用程序中不同的表做区分的,通常会添加到 authority 的后面。比如某个应用的数据库里存在两张表 table1 和 table2,这时就可以将 path 分别命名为 /table1 和 /table2,然后把 authority 和 path 进行组合,内容 URI 就变成了com.example.app.provider/table1com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容 URI,我们还需要在字符串的头部加上协议声明。因此,内容URI 最标准的格式如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2

        // 解析成 Uri 对象
        val uri = Uri.parse("content://com.example.app.provider/table1")

        // 查询 table1 表中的数据
        val cursor = contentResolver.query(
            uri, 
            projection,
            selection,
            selectionArgs,
            sortOrder
        )
  • uri:对应SQL部分:from table_name。指定查询某个应用程序下的某一张表
  • projection:对应SQL部分:select column1, column2。指定查询的列名
  • selection:对应SQL部分:where column = value。指定where的约束条件
  • selectionArgs:为where中的占位符提供具体的值
  • sortOrder:对应SQL部分:order by column1, column2。指定查询结果的排序方式

取出每一行中相应列的数据

        while (cursor.moveToNext()) {
            val column1 = cursor.getString(cursor.getColumnIndex("column1"))
            val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
        }
        cursor.close()

添加一条数据

        val values = contentValuesOf("column1" to "text", "column2" to 1)
        contentResolver.insert(uri, values)

更新数据

        val values = contentValuesOf("column1" to "")
        contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", 1))

删除数据

        contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
读取系统联系人

AndroidManifest.xml 添加权限

class MainActivity : AppCompatActivity() {

    private val contactList = ArrayList()
    private lateinit var adapter: ArrayAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactList)
        findViewById(R.id.listView).adapter = adapter

        if (ContextCompat.checkSelfPermission(
                this,
                android.Manifest.permission.READ_CONTACTS
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            // 申请授权。第三个参数为请求吗,只要是唯一值就可以了
            ActivityCompat.requestPermissions(
                this,
                arrayOf(android.Manifest.permission.READ_CONTACTS),
                99999
            )
        } else {
            readContacts()
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        when (requestCode) {
            99999 -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    readContacts()
                } else {
                    Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    @SuppressLint("Range")
    private fun readContacts() {
        // 查询联系人数据
        contentResolver.query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null,
            null,
            null,
            null
        )?.apply {
            while (moveToNext()) {
                // 获取联系人姓名
                val displayName =
                    getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                // 获取联系人手机号
                val number =
                    getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
                contactList.add("$displayName\n$number")
            }
            adapter.notifyDataSetChanged()
            close()
        }
    }
}

创建自己的 ContentProvider

想要实现跨程序共享数据的功能,可以通过新建一个类去继承 ContentProvider 的方式来实现。

class MyProvider : ContentProvider {

    // 初始化 ContentProvider 的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回 true 表示 ContentProvider 初始化成功,返回 false 则表示失败。
    override fun onCreate(): Boolean {
        return false
    }

    // 从 ContentProvider 中查询数据。uri 参数用于确定查询哪张表,projection 参数用于确定查询哪些列,selection 和 selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在 Cursor 对象中返回。
    override fun query(
        p0: Uri,
        p1: Array?,
        p2: String?,
        p3: Array?,
        p4: String?
    ): Cursor? {
        return null
    }

    // 根据传人的内容 URI 返回相应的 MIME 类型。
    override fun getType(p0: Uri): String? {
        return null
    }

    // 向 ContentProvider 中添加一条数据。uri 参数用于确定要添加到的表,待添加的数据保存在 values 参数中。添加完成后,返回一个用于表示这条新记录的 URI。
    override fun insert(p0: Uri, p1: ContentValues?): Uri? {
        return null
    }

    // 从 ContentProvider 中删除数据。uri 参数用于确定删除哪一张表中的数据,selection 和 selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。
    override fun delete(p0: Uri, p1: String?, p2: Array?): Int {
        return 0
    }

    // 更新 ContentProvider 中已有的数据。uri 参数用于确定更新哪一张表中的数据,新数据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。
    override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array?): Int {
        return 0
    }
}

一个标准的内容 URI 写法是 content://com.example.app.provider/table1。这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中的数据。

在内容 URI 的后面加上一个id:content://com.example.app.provider/table1/1。表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据。

  • *表示匹配任意长度的任意字符
  • #表示匹配任意长度的数字

content://com.example.app.provider/*:表示一个能够匹配任意表的内容URI格式。

content://com.example.app.provider/table1/#:表示一个能够匹配 table1 表中任意一行数据的内容URI格式。

使用 UriMatcher 实现匹配内容 URI 的功能
class MyProvider : ContentProvider {

    private val tableDir = 0
    private val tableItem = 1
    private val table2Dir = 2
    private val table2Item = 3

    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)

    init {
        uriMatcher.addURI("com.example.app.provider", "table1", tableDir)
        uriMatcher.addURI("com.example.app.provider", "table1/#", tableItem)
        uriMatcher.addURI("com.example.app.provider", "table12", table2Dir)
        uriMatcher.addURI("com.example.app.provider", "table12/#", table2Item)
    }

    // insert()、delete()、update()
    override fun query(
        p0: Uri,
        p1: Array?,
        p2: String?,
        p3: Array?,
        p4: String?
    ): Cursor? {
        when (uriMatcher.match(p0)) {
            tableDir -> {
                // 查询 table1 表中的所有数据
            }
            tableItem -> {
                // 查询 table1 表中的单条数据
            }
            table2Dir -> {
                // 查询 table2 表中的所有数据
            }
            table2Item -> {
                // 查询 table2 表中的单条数据
            }
            ......
        }
        ......
    } 
}
getType()

用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由3部分组成:

  1. 必须以 vnd 组成
  2. 如果内容 URI 以路径结尾,则后接 android.cursor.dir/;如果内容 URI 以 id 结尾,则后接 android.cursor.item/
  3. 最后接上 vnd..

content://com.example.app.provider/table1 对应的 MIME 类型为:vnd.android.cursor.dir/vnd.com.example.app.provider.table1

content://com.example.app.provider/table1/1 对应的 MIME 类型为:vnd.android.cursor.item/vnd.com.example.app.provider.table1

getType() 方法中的逻辑:

    override fun getType(p0: Uri) = when (uriMatcher.match(p0)) {
        tableDir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
        tableItem -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
        table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
        table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
        else -> null
    }

实现跨程序数据共享

创建一个 ContentProvider:右击->New->Other->Content Provider

image.png
  • Exported:表示是否允许外部程序访问我们的 ContentProvider
  • Enable:表示是否启用这个 ContentProvider

创建完毕后 AndroidManifest.xml 内容如下:




    

        

        
            
                

                
            
        
    


DatabaseProvider

class DatabaseProvider : ContentProvider() {

    private val bookDir = 0
    private val bookItem = 1
    private val categoryDir = 2
    private val categoryItem = 3
    private val authority = "com.gaowenli.databasetest.provider"
    private var dbHelper: MyDatabaseHelper? = null

    private val uriMatcher by lazy {
        val matcher = UriMatcher(UriMatcher.NO_MATCH)
        matcher.addURI(authority, "book", bookDir)
        matcher.addURI(authority, "book/#", bookItem)
        matcher.addURI(authority, "category", categoryDir)
        matcher.addURI(authority, "category/#", categoryItem)
        matcher
    }

    override fun onCreate() = context?.let {
        dbHelper = MyDatabaseHelper(it, "BookStore.db", 1)
        true
    } ?: false

    override fun query(
        uri: Uri, projection: Array?, selection: String?,
        selectionArgs: Array?, sortOrder: String?
    ) = dbHelper?.let {
        val db = it.readableDatabase
        val cursor = when (uriMatcher.match(uri)) {
            bookDir -> db.query("Book", projection, selection, selectionArgs, null, null, sortOrder)
            bookItem -> {
                val bookId = uri.pathSegments[1]
                db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
            }
            categoryDir -> db.query(
                "Category",
                projection,
                selection,
                selectionArgs,
                null,
                null,
                sortOrder
            )
            categoryItem -> {
                val categoryId = uri.pathSegments[1]
                db.query(
                    "Category",
                    projection,
                    "id = ?",
                    arrayOf(categoryId),
                    null,
                    null,
                    sortOrder
                )
            }
            else -> null
        }
        cursor
    }

    override fun insert(uri: Uri, values: ContentValues?) = dbHelper?.let {
        val db = it.writableDatabase
        val uriReturn = when (uriMatcher.match(uri)) {
            bookDir, bookItem -> {
                val newBookId = db.insert("Book", null, values)
                Uri.parse("content://$authority/book/$newBookId")
            }
            categoryDir, categoryItem -> {
                val categoryId = db.insert("Category", null, values)
                Uri.parse("content://$authority/category/$categoryId")
            }
            else -> null
        }
        uriReturn
    }

    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array?
    ) = dbHelper?.let {
        val db = it.writableDatabase
        val updatedRows = when (uriMatcher.match(uri)) {
            bookDir -> db.update("Book", values, selection, selectionArgs)
            bookItem -> {
                val bookId = uri.pathSegments[1]
                db.update("Book", values, "id = ?", arrayOf(bookId))
            }
            categoryDir -> db.update("Category", values, selection, selectionArgs)
            categoryItem -> {
                val categoryId = uri.pathSegments[1]
                db.update("Category", values, "id = ?", arrayOf(categoryId))
            }
            else -> 0
        }
        updatedRows
    } ?: 0

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array?) =
        dbHelper?.let {
            val db = it.writableDatabase
            val deletedRows = when (uriMatcher.match(uri)) {
                bookDir -> db.delete("Book", selection, selectionArgs)
                bookItem -> {
                    val bookId = uri.pathSegments[1]
                    db.delete("Book", "id = ?", arrayOf(bookId))
                }
                categoryDir -> db.delete("Category", selection, selectionArgs)
                categoryItem -> {
                    val categoryId = uri.pathSegments[1]
                    db.delete("Category", "id = ?", arrayOf(categoryId))
                }
                else -> 0
            }
            deletedRows
        } ?: 0

    override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
        bookDir -> "vnd.android.cursor.dir/vnd.com.gaowenli.databasetest.provider.book"
        bookItem -> "vnd.android.cursor.item/vnd.com.gaowenli.databasetest.provider.book"
        categoryDir -> "vnd.android.cursor.dir/vnd.com.gaowenli.databasetest.provider.category"
        categoryItem -> "vnd.android.cursor.item/vnd.com.gaowenli.databasetest.provider.category"
        else -> null
    }
}

MyDatabaseHelper

class MyDatabaseHelper(context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {

    private val createBook = "create table Book (" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text," +
            "catetory_id integer)" 

    override fun onCreate(p0: SQLiteDatabase?) {
        p0?.execSQL(createBook) 
    }

    override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {

    }
}

创建一个新的项目 ProviderTest

AndroidManifest.xml 添加 queries




    
        
    

    

        
            
                

                
            
        
    


访问共享程序中的数据

class MainActivity : AppCompatActivity() {

    var bookId: String? = null

    @SuppressLint("Range")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById

《第一行代码Android》


FileProvider 应用安装

AndroidManifest.xml




    
    
    

    
        
            
            

        

    


file_paths.xml



    
    

    
    

PermissionUtil

public class PermissionUtil {

    // 检查多个权限。返回true表示已完全启用权限
    public static boolean checkPermission(Activity activity, String[] permissions, int requestCode) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int check = PackageManager.PERMISSION_GRANTED;
            for (String permission : permissions) {
                check = ContextCompat.checkSelfPermission(activity, permission);
                if (check != PackageManager.PERMISSION_GRANTED) {
                    break;
                }
            }

            if (check != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(activity, permissions, requestCode);
                return false;
            }
        }
        return true;
    }
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String[] PERMISSIONS = new String[]{
            Manifest.permission.READ_EXTERNAL_STORAGE
    };
    private static final int PERMISSION_REQUEST_CODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button).setOnClickListener(this);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case PERMISSION_REQUEST_CODE:
                installApk();
                break;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.R)
    private void checkAndInstall() {
        // 检查是否拥有 MANAGE_EXTERNAL_STORAGE 权限,没有则跳转到设置页面
        if (!Environment.isExternalStorageManager()) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setData(Uri.fromParts("package", getPackageName(), null));
        } else {
            installApk();
        }
    }

    private void installApk() {
        String apkPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/app-release.apk";

        // 获取应用包管理器
        PackageManager packageManager = getPackageManager();
        // 获取apk文件的包信息
        PackageInfo info = packageManager.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
        if (info == null) { // 安装文件损坏
            return;
        }

        Uri uri = Uri.parse(apkPath);
        // 兼容Android7.0,把访问文件的Uri方式改为FileProvider
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // 通过FileProvider获得文件的Uri访问方式
            uri = FileProvider.getUriForFile(this, "com.example.mycontentprovider.fileProvider", new File(apkPath));
        }
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        // 设置Uri的数据类型为APK文件
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        // 启动系统自带的应用安装程序
        startActivity(intent);
    }

    @Override
    public void onClick(View v) {
        // Android11之后获取 MANAGE_EXTERNAL_STORAGE 权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            checkAndInstall();
        } else {
            // 如果有权限,直接安装,没有权限则获取权限
            if (PermissionUtil.checkPermission(this, PERMISSIONS, PERMISSION_REQUEST_CODE)) {
                installApk();
            }
        }
    }
}

你可能感兴趣的:(Android ContentProvider、FileProvider)