Android 内容提供器

本文详细介绍了Android中的内容提供器,包括如何访问其他程序数据、创建自己的内容提供器及其核心方法如query、insert、update和delete。内容提供器通过ContentResolver与Uri进行数据操作,并确保数据隐私安全。
摘要由CSDN通过智能技术生成

Android 内容提供器

访问其他程序中的数据

引言

如果想要访问内容提供器中共享的数据,需借助ContentResolver类,可以通过Context中的getContentResolver方法获取该类的实例,该类中提供了一系列的方法便于对数据进行增(insert)删(delete)改(update)查(query)操作,是不是跟数据库很像,但是区别还是有的,而且不小!!!这个类不接受表名作为参数,而是接受一个Uri作为参数,该Uri由两部分组成:其一是目标APP的包名加上”.provider“字符串,其二是具体的表,这样就细致到了每一个APP的每一个表,不会冲突!!当然,最标准的Uri的写法还要加上协议声明,所以Uri最标准的格式为:content://com.example.app.provider/table1

得到Uri之后,还要将它解析成Uri对象才可以作为参数传入,用Uri.parse方法就能实现,传入标准的Uri就能自动解析了,然后就可以用得到的解析完成的Uri对象了!

查询:

Cursor cursor = getContentResolver().query(uri , projection , selection , selectionArgs , sortOrder);
query()方法参数对应SQL部分描述
urifrom table_name指定查询某个应用程序下的某一张表
projectionselect column1 , column2指定查询的列名
selectionwhere column = value指定where的约束条件
selectionArgs-为where中的占位符提供具体的值
sortOrderorder by column1 , column2指定查询结果的排列方式

可以对返回的Cursor对象进行遍历得到想要的数据!如:

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

增加:

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

更新:

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

删除:

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

实例

代码实例

编写修改activity_main.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/contacts_view"/>

</LinearLayout>

编写修改MainActivity.java文件:

public class MainActivity extends AppCompatActivity {

    private ArrayAdapter<String> adapter;

    private List<String> contactsList = new ArrayList<>();

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

        ListView contactsView = 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(this , new String[] {Manifest.permission.READ_CONTACTS} , 1);
        } else {
            readContacts();
        }
    }

    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();
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1 :
                if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    readContacts();;
                }
                else {
                    Toast.makeText(this , "嘿,想用就得打开权限!!" , Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }
}

先是获取了ListView控件的实例,给他设置适配器,然后因为此处READ_CONTACTS为危险权限,所以要进行申请

对readContacts方法分析:看到query方法中传入的Uri参数很是不一样,为什么没有调用Uri.parse()方法进行解析呢?因为这个ContactsContract.CommonDataKinds.Phone已经帮我们封装好了,提供一个CONTENT_URI常量,这个常量就是通过Uri.parse()方法解析得到的!然后遍历查询结果cursor,然后姓名和电话号码就如代码中传入的两个常量,得到想要的数据后,拼接,添加到ListView的数据源中即可!最后要关闭cursor!!!

编写修改AndroidManife.xml文件:

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

    <uses-permission android:name="android.permission.READ_CONTACTS"/>

    。。。
    
</manifest>
成果展示

在这里插入图片描述

进入APP的界面就开始申请权限!!!

在这里插入图片描述

同意之后就是这样啦!小姐姐的电话咋能告诉你们嘿嘿嘿

创建自己的内容提供器

引言

右键MainActivity所在的包选择New、Other、ContentProvider,然后内容提供器的名字自己决定,authority也自己决定,当然不要太随意,后面会用到的!!新建完成,已经给你写好了6个方法:
onCreate():初始化内容提供器的时候调用。通常完成对数据库的创建和升级等操作,返回true表示成功,反之则表示失败

query():从内容提供器中查询数据。使用uri参数来确定查询哪张表,projection参数用于确定哪些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,返回Cursor包含查询结果的对象

insert():插入一条数据。使用uri参数来确定添加到哪张表,values用于存储插入的数据,添加完成返回一个用于表示这条新纪录的URI

update():更新已有数据。使用uri参数来确定更新哪张表,values用于存储更新的数据,selection和selectionArgs参数用于约束查询哪些行,返回受到影响的行数

delete():删除已有数据。使用uri参数来确定删除哪张表,selection和selectionArgs参数用于约束查询哪些行,返回被删除的行数

getType():根据传入的内容URI来返回相应的MINE类型

回顾一下,一个标准的内容URI写法是这样的:content://com.example.app.provider/table1,这就表示我们希望查询的是table1表中的数据,我们还能在这个URI后面加一个id,如:content://com.example.app.provider/table1/1,表示查询table1中id为1的数据;内容URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中的所有数据,以id结尾具体到某一行,我们可以使用通配符的方式来分别匹配这两种格式的URI,如:*:表示匹配任意长度的任意字符;#:表示匹配任意长度的数字;接着我们再借助UriMatcher这个类就可以很轻松实现匹配内容URI的功能,这个类提供了一个addURI方法,参数有三,分别为authority、path、一个自定义代码。这样当调用UriMatcher的match方法是,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方希望访问的是哪张表的数据了,大概就是这个样子:

public class MyProvider extends ContentProvider {

    public static final int TABLE1_DIR = 0;

    public static final int TABLE1_ITEM = 1;

    public static final int TABLE2_DIR = 2;

    public static final int TABLE2_ITEM = 3;

    private static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider" , "table1" , TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider" , "table1/#" , TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider" , "table2" , TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider" , "table2/#" , TABLE2_ITEM);
    }

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

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                //查询table1表中的所有数据
                break;
            case TABLE1_ITEM:
                //查询table1表中的单条数据
                break;
            case TABLE2_DIR:
                //查询table2表中的所有数据
                break;
            case TABLE2_ITEM:
                //查询table2表中的单条/数据
                break;
            default:
        }
    }
    
    。。。
        
}

针对getType方法:用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成,格式规定:

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

代码示例:

@Nullable
@Override
public String getType(@NonNull Uri uri) {
    switch (uriMatcher.match(uri)) {
        case TABLE1_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
        case TABLE1_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
        case TABLE2_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
        case TABLE2_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
    }
}

接下来从代码实例分析被调用方各个方法中的细节:

public class DatabaseProvider extends ContentProvider {

    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;
    public static final String AUTHORITY = "com.example.databasetest.provider";
    private static UriMatcher uriMatcher;
    private MyDatabaseHelper dbHelper;

    static {
        uriMatcher = new UriMatcher(uriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY , "book" , BOOK_DIR);
        uriMatcher.addURI(AUTHORITY , "book/#" , BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY , "category" , CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY , "category/#" , CATEGORY_ITEM);
    }

    public DatabaseProvider() {
    }

    @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        dbHelper = new MyDatabaseHelper(getContext() , "BookStore.db" , null , 2);
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                cursor = db.query("Book" , projection , selection , selectionArgs , null , null , sortOrder);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1); //将内容URI权限之后的部分以"/"符号进行分割,并将其放入字符串列表中
                cursor = db.query("Book" , projection , "id = ?" , new String[]{bookId} , null , null , sortOrder);
                break;
            case CATEGORY_DIR:
                cursor = db.query("Category" , projection , selection , selectionArgs , null , null , sortOrder);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("Category" , projection , "id = ?" , new String[]{categoryId} , null , null , sortOrder);
                break;
            default:
                 break;
        }
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("Book" , null , values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category" , null , values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
                break;
            default:
                break;
        }
        return uriReturn;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updateRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                updateRows = db.update("Book" , values , selection , selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updateRows = db.update("Book" , values , "id = ?" , new String[]{bookId});
                break;
            case CATEGORY_DIR:
                updateRows = db.update("Category" , values , selection , selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updateRows = db.update("Category" , values , "id = ?" , new String[]{categoryId});
            default:
                break;
        }
        return updateRows;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deleteRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                deleteRows = db.delete("Book" , selection , selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deleteRows = db.delete("Book" , "id = ?" , new String[]{bookId});
                break;
            case CATEGORY_DIR:
                deleteRows = db.delete("Category" , selection , selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deleteRows = db.delete("Category" , "id = ?" , new String[]{categoryId});
                break;
            default:
                break;
        }
        return deleteRows;
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
        }
        return null;
    }
}

在query方法中调用了Uri对象的getPathSegments方法,这个会将内容URI权限之后的部分以”/“符号进行分割,将结果放入字符串列表中,0存放的是路径,1存放的是id

真的,其他都是一个样子的,炒鸡简单!!!!!

还有一件事,内容提供器需要在AndroidManifest.xml中注册嗷!当然啦,通过我讲的这个方式新建的系统会帮我们注册!

接下来从代码实例分析调用者各个方法中的细节:

public class MainActivity extends AppCompatActivity {

    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button addData = findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name" , "A Clash of Kings");
                values.put("author" , "George of kings");
                values.put("pages" , 1040);
                values.put("price" , 22.85);
                Uri newUri = getContentResolver().insert(uri , values);
                newId = newUri.getPathSegments().get(1);
            }
        });

        Button queryData = findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                Cursor cursor = getContentResolver().query(uri , null , null , null , null);
                if(cursor != null) {
                    while(cursor.moveToNext()) {
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String author = cursor.getString(cursor.getColumnIndex("author"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        double price = cursor.getDouble(cursor.getColumnIndex("price"));
                        Log.d("MainActivity" , "book name is " + name);
                        Log.d("MainActivity" , "book author is " + author);
                        Log.d("MainActivity" , "book pages is " + pages);
                        Log.d("MainActivity" , "book price is " + price);
                    }
                    cursor.close();
                }
            }
        });

        Button updateData = findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name" , "A Storm of Swords");
                values.put("pages" , 1216);
                values.put("price" , 24.05);
                getContentResolver().update(uri , values , null , null);
            }
        });

        Button deleteData = findViewById(R.id.delete_data);
        deleteData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                getContentResolver().delete(uri , null , null);
            }
        });
    }
}

这里的代码就更不用说了,太简单了!!

小结

简单来看,内容提供器即提供数据也提供接口,我们可以很方便的调用接口进行访问及修改!!

那么是如何保证隐私数据不会泄露的呢?因为所有的CRUD操作都一定要匹配到相应的内容URI格是才能进行的,而我们不可能向UriMatcher中添加隐私数据的URI,所以其他部分数据无法被外部程序所访问。一句话概括,我给你的就是你的,我不给的你就别想了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值