笔记:Android Content Provider内容提供器

目前所有的信息数据都只能在当前应用程序中访问,不能被其他应用程序访问,Antroid 提供了内容提供器(Content Provider)机制,允许一个程序访问另一个程序中的数据,同时保证被访问数据的安全性,内容提供器使用时会用到运行时权限功能

运行时权限

Android 6.0 系统中引用了运行时权限这个功能,从而更好保护用户安全和隐私,下列是危险权限,一共9组24个权限,剩下的都是普通权限

权限组名权限名
CALENDAR(日历)READ_CALENDAR
WRITE_CALENDAR
CAMERA(相机)CAMERA
CONTACTS(联系人)READ_CONTACTS
WRITE_CONTACTS
GET_CONTACTS
LOCATION(位置)ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MICROPHONE(麦克风)RECORD_AUDIO
PHONE(手机)READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SENSORS(短信)BODY_SENSORS
SMS(传感器)SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
STORAGE(存储卡)READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

如果是这张表中的权限,就需要进行运行时权限处理,如果不是就可以在 AndroidManifest.xml 文件中添加权限的声明
注意:一旦用户授权的其中的一个权限,那么整个权限组中的其他权限也会同时被授权

申请运行时权限
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        Button button = (Button) findViewById(R.id.content_button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (ContextCompat.checkSelfPermission(ContentProvider.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(ContentProvider.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
                }else {
                    call();
                }
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    call();
                }else {
                    Toast.makeText(this, "未授权", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

    private void call(){
        Intent intent = new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:10086"));
        startActivity(intent);
    }

运行时权限的核心就是在程序运行的过程中由用户去执行危险操作,第一步通过 ContextCompat.checkSelfPermission() 方法判断用户是不是已经授权过了,接收两个参数,第一个是一个上下文 Context,第二个是具体权限名,这里是打电话的权限就是 Manifest.permission.CALL_PHONE,最后与 PackageManager.PERMISSION_GRANTED 作比较,相等表示已经授权

如果未授权,需要调用 ActivityCompat.requestPermissions() 方法向用户去申请授权,接收3个参数,第一个是 Activity 实例,第二个是一个 String 数组,数组里放的是申请的权限名,第三个是请求码,请求码至于要唯一就行

调用完 ActivityCompat.requestPermissions() 后,系统会弹出一个授权申请的对话框,用户可以选择同一或拒绝申请,无论那种结果,都会回掉到 onRequestPermissionsResult() 方法中,授权结果封装在 grantResults 中,判断一下授权结果,用户授权同意就调用 call() 方法进行打电话,否则弹出一条失败信息

别忘了在 AndroidManifest.xml 文件中添加打电话的权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.helloworld">
    
    <uses-permission android:name="android.permission.CALL_PHONE" />
	.......
</manifest>

ContentResolver 基本用法

内容提供器有两种用法:一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给程序的数据提供外部访问的接口

对于每一个应用程序来说想要访问内容提供器中的共享数据时,要借助 ContentResolver 类,可以通过 Context 中的 getContentResolver() 方法来获取该类的实例,ContentResolver 中提供了一系列方法对数据进行 CRUD 操作,insert() 方法用于添加数据,update() 方法用于更新数据,delete() 方法用于删除数据,query() 方法用于查询数据,这些方法接收一个 Uri 参数,内容Uri 给内容提供器中的数据建立了唯一的表示符,由两部分组成:authority 和 path,authority 是用于对不同的应用程序作区分,path 则是用于对统一应用程序中的不同表做区分通常添加到 authority后面,还需要在头部加上协议声明 content://<packagename>/<tablename>在有了Uri 字符串之后要将它解析成 Uri 对象

	Uri uri = Uri.parse("content://com.example.helloworld.provider/book");
	Cursor cursor = getContentResolver.query(uri, projection, selection, selectionArgs, sortOrder);
query()方法参数描述
uri指定查询某个应用程序下的某张表
projection指定查询的列名
selection指定where的约束条件
selectionArgs为where的占位符提供具体数值
sortOrder指定查询结果的排序方式

查询完后返回一个 Cursor 对象,就可以从这个对象中读出数据

if(cursor != null){
	while(cursor.moveToNext()){
		String column1 = cursor.getString(cursor.getColumnIndex("column1"));
		int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
	}
	cursor.close();
}

添加

ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);

将待添加的数据组装到 ContentValues 中,然后调用 insert() 方法将 Uri 和 ContentValues 作为参数传入即可
更新

ContentValues values = new ContentValues();
values.put("column1", "text1111");
values.put("column2", 2;
getContentResolver().update(uri, values, "column1=? and column2=?", new String[] {"text", "1"});

不传如约束条件 selection 和 selectionArgs 将会将所有的数据进行更新
删除

getContentResolver().delete(uri, values, "column2=?", new String[] {"1"});

同样不传入约束添加将会删除所有数据

读取联系人

先在布局文件中建立一个 ListView,将读出的联系人在 ListView 中显示

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
    
    <ListView
        android:id="@+id/contacts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </ListView>

</LinearLayout>

同样在 onCreate() 方法中先获取 ListView 控件的实例,并添加上适配器,然后调用运行时权限处理逻辑,用户授权后调用 readContacts() 方法来读取联系人

    ArrayAdapter<String> adapter;
    List<String> contactsList = new ArrayList<>();

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        
        ListView contactsView = (ListView)findViewById(R.id.contacts_view);

        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
        contactsView.setAdapter(adapter);

        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(ContentProvider.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
        }else {
            readContacts();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    readContacts();
                }else {
                    Toast.makeText(this, "未授权"+grantResults.length+"::"+grantResults[0], Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

    private void readContacts(){
        Cursor cursor = null;
        try{
            cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
            if (cursor != null){
                while (cursor.moveToNext()){
                    String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add(displayName + "\n" + number);
                }
                adapter.notifyDataSetChanged();
            }
        } catch (Exception e){
            e.printStackTrace();
        }finally {
            if (cursor != null){
                cursor.close();
            }
        }
    }

readContacts() 方法中使用了 getContentResolver() 方法获得 ContentResolver 对象,又调用了 ContentResolver 中的 query() 方法查询系统的联系人数据,查询出来是一个 Cursor 对象,接着对这个 Cursor 对象进行遍历,将联系人姓名和电话号码取出添加到 ListView 的数据源里,并通知刷新 ListView,最后关闭 Cursor 对象
最后对系统读取联系人的权限在 AndroidManifest.xml 文件中进行声明

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.helloworld">
    
    <uses-permission android:name="android.permission.READ_CONTACTS" />
	.......
</manifest>

在这里插入图片描述

创建自定义内容提供器

通过创建一个类去继承 ContentProvider 的方法来创建一个自定义的内容提供器, ContentProvider 有6个抽象方法,需要全部重写

public class MyProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}
  1. onCreate()
    初始化内容提供器的时候调用,通常用于对数据库的创建和升级等操作,返回 true 表示内容提供器初始化成功,false 表示失败,注意,只有 ContentResolver 尝试访问程序中的数据时,内容提供器才会被初始化
  2. query()
    从内容提供器中查询数据,使用 uri 参数来确定查询那张表,projection 参数用于确定查询那些列,selection 和 selectionArgs 参数用于约束查询条件,sortOrder 参数用于对查询结果排序,查询结果放在 Cursor 对象中返回
  3. update()
    更新内容提供器中是数据,使用 uri 参数来确定更新那一张表中的数据,新数据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新的条件,返回受影响的行数
  4. insert()
    向内容提供器中添加一条数据,使用uri参数来确定要添加到那张表,待添加数据保存在 values 参数中,添加完成后返回一个用于表达这条新数据的 URI
  5. delete()
    从内容提供器中删除数据,使用uri 参数来确定删除那一张表中的数据,selection 和 selectionArgs 参数用于约束删除的条件,返回被删除的行数
  6. getType()
    根据传入的内容URI来返回相应的MIME类型

可以看到每个方法都有一个 Uri 参数,这个参数就是调用 ContentResolver 的增删改查方法时传递过来的,内容URI 的格式有两种,一种是以路径结尾表示访问该表中的所有数据,一种是以id 结尾,表示访问该表中对应id 的数据

UriMatcher 这个类可以轻松实现匹配内容URI的功能,UriMatcher 中提供了一个 addURI() 方法,这个方法接受3个参数,第一个是 authority,第二个是 path,第三个是 一个自定义的代码,再调用 UriMatcher 的 match() 方法,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri 对象所对应的自定义代码

	public static final int BOOK_DRI = 0;
    public static final int BOOK_ITEM = 1;

    private static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.helloworld.provider", "book", BOOK_DRI);
        uriMatcher.addURI("com.example.helloworld.provider", "book/#", BOOK_ITEM);
    }

    //......

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        switch (uriMatcher.match(uri)){
            case BOOK_DRI:
                //查询表中所有的数据
                break;
            case BOOK_ITEM:
                //查询表中单条数据
                break;
        }
        return null;
    }
    //.......

这样就可以很方便的判断用户要的是什么数据,不用拆分 Uri 再用一大堆 if 进行判断,而且能够被访问的数据都放进了uriMatcher 对象里,不用判断哪些是合法请求,哪些是非法请求。在静态代码块中创建了 UriMatcher 的实例,并调用 addURI() 方法将期望匹配的内容URI格式传递进去,当调用 query() 方法的时候,通过 UriMatcher 的 match() 方法就可以对传入的Uri对象进行匹配,如果配对成功就返回相应的自定义代码,就可以知道用户期望访问的数据了,inster()、update()、delete() 都是一样的判断

getType() 方法是所有内容提供器都必须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型,一个内容URI所对应的 MIME 字符串由3部分组成,第一必须由 vnd 开头,第二如果是以路径结尾,则后接 android.cursor.dir/,如果是以 id 结尾,则后接 android.cursor.item/ 第三最后接上 vnd.<packagename>.<path>
所以 content://com.example.helloworld.provider/book 对应的 MIME 类型是vnd.android.cursor.dir/vnd.com.example.helloworld.provider/book
content://com.example.helloworld.provider/book/1 对应的 MIME 类型是vnd.android.cursor.item/vnd.com.example.helloworld.provider/book

	@Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)){
            case TABLE_DRI:
                return "vnd.android.cursor.dir/vnd.com.example.helloworld.provider/book";
            case TABLE_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.helloworld.provider/book";
        }
        return null;
    }

前面查询数据库可以使用 LitePal 的方法,但是 LitePal 查询结果是一个 Book 的实体对象,而 ContentProvider 返回的是一个 Cursor 对象,需要进行转变,就不如直接使用继承 SQLiteOpenHelper 方法,这样查询出来的直接就是一个 Cursor 对象。

public class MyProvider extends ContentProvider {

    public static final int TABLE_DRI = 0;
    public static final int TABLE_ITEM = 1;

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper myDatabaseHelper;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.helloworld.provider", "book", TABLE_DRI);
        uriMatcher.addURI("com.example.helloworld.provider", "book/#", TABLE_ITEM);
    }

    @Override
    public boolean onCreate() {
        myDatabaseHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 5);
        return true;
    }

	// 查询数据
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] prjection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d("zwt", "进入查询"+uriMatcher.match(uri));
        SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)){
            case TABLE_DRI:
                //查询表中所有的数据
                cursor = db.query("Book", prjection, selection, selectionArgs, null, null, sortOrder);
                Log.d("zwt", "TABLE_DRI:cursor是否为空:"+String.valueOf(cursor==null));
                break;
            case TABLE_ITEM:
                //查询表中单条数据
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("Book", prjection, "id=?", new String[]{bookId}, null, null, sortOrder);
                Log.d("zwt", "TABLE_ITEM:cursor是否为空:"+String.valueOf(cursor==null));
                break;
        }
        return cursor;
    }

	// 插入数据
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
    	Log.d("zwt", "进入插入数据"+uriMatcher.match(uri));
        SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)){
            case TABLE_DRI:
            case TABLE_ITEM:
                long newBookId = db.insert("Book", null, contentValues);
                uriReturn = Uri.parse("content://com.example.helloworld.provider/book/" + newBookId);
                break;
        }
        return uriReturn;
    }

	// 删除数据
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
    	Log.d("zwt", "进入删除数据"+uriMatcher.match(uri));
        SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
        int deleteRows = 0;
        switch (uriMatcher.match(uri)){
            case TABLE_DRI:
                deleteRows = db.delete("Book", selection, selectionArgs);
                break;
            case TABLE_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deleteRows = db.delete("Book", "id=?", new String[]{bookId});
                break;
        }
        return deleteRows;
    }

	// 更新数据
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
    	Log.d("zwt", "进入更新数据"+uriMatcher.match(uri));
        SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)){
            case TABLE_DRI:
                updatedRows = db.update("Book", contentValues, selection, selectionArgs);
                break;
            case TABLE_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book", contentValues, "id=?", new String[]{bookId});
                break;
        }
        return updatedRows;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)){
            case TABLE_DRI:
                return "vnd.android.cursor.dir/vnd.com.example.helloworld.provider/book";
            case TABLE_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.helloworld.provider/book";
        }
        return null;
    }
}

最后,内容提供器一定要在 AndroidManifest.xml 文件中进行注册才能使用

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.helloworld">

    <application
        android:name="org.litepal.LitePalApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ......something code
        <provider
            android:authorities="com.example.helloworld.provider"
            android:name="com.example.contentprovider.MyProvider"
            android:enabled="true"
            android:exported="true">

        </provider>
    </application>
</manifest>

android:enabled=“true” 该属性表明了该content provider是否可以被实例化
android:exported=“true” 该属性指示了content provider是否可以被其他应用程序使用

建立一个新的项目,创建4个按钮用于增删改查

		private String newId;
		
		addDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.helloworld.provider/book");

                ContentValues values = new ContentValues();
                values.put("name", "33333");
                values.put("author", "ccc");
                values.put("price", 110.90);
                values.put("press", "Unknow");

                Uri newUri = getContentResolver().insert(uri, values);
                newId = newUri.getPathSegments().get(1);
                Toast.makeText(MainActivity.this, "添加数据成功", Toast.LENGTH_SHORT).show();
            }
        });
        deleteDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.helloworld.provider/book/"+newId);
                getContentResolver().delete(uri, null, null);
                Toast.makeText(MainActivity.this, "删除数据成功", Toast.LENGTH_SHORT).show();
            }
        });
        updateDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.helloworld.provider/book/"+newId);
                ContentValues values = new ContentValues();
                values.put("press", "China");
                getContentResolver().update(uri, values, null, null);
                Toast.makeText(MainActivity.this, "更新数据成功", Toast.LENGTH_SHORT).show();
            }
        });
        queryDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.helloworld.provider/book");
                Cursor cursor = getContentResolver().query(uri, null, null, null, null);
                if(cursor!=null){
                    String name = null;
                    String author = null;
                    double price = 0;
                    while(cursor.moveToNext()){
                        name = cursor.getString(cursor.getColumnIndex("name"));
                        author = cursor.getString(cursor.getColumnIndex("author"));
                        price = cursor.getDouble(cursor.getColumnIndex("price"));
                    }
                    cursor.close();
                    Toast.makeText(MainActivity.this, "书名:"+name+"作者::"+author+"价格::"+price, Toast.LENGTH_SHORT).show();
                }
            }
        });

在这里插入图片描述
可以看到在 test 这个应用中可以操作 helloworld 应用中的数据

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值