[Android]Android四大组件之ContentProvider
URI简介
URI(Universal Resource Identifier),又被称为"通用资源标志符"。
URI由许多部分所组成,示例及解说如下:

Content URIs介绍
Android遵循URI的标准,定义了一套专用的Uri(即,Content URIs)。并且,Android提供了ContentUris、UriMatcher等类用于操作Content URIs。
1. Content URIs语法
Content URIs的语法如下:
content://authority/path/id
Content URIs的示例及说明如下:

说明:
content: Content URIs前缀,它对应与标准URI的scheme。它的值为ContentResolver.SCHEME_CONTENT(即,content://)。
authority: 一个唯一的标识符,Google建议使用类的全名来作为authority。外部调用者可以根据这个标识来找到它。
path: 它可以用来表示我们要操作的数据,外部调用者根据这个路径信息来判断要返回什么类型的数据。这个后缀路径可以自由定义。
id: 唯一的数字标识符。它表示要具体操作的数据类型中的具体某一项。
2. Content URIs相关类介绍
2.1 ContentUris
ContentUris中包含了三个静态函数:
long parseId(Uri uri): 解析Uri中的末尾id。成功返回id,失败则返回-1。
Uri withAppendedId(Uri uri, long id): 将id追加到uri中,并返回追加id后的uri。
Uri.Builder appendId(Uri.Builder builder, long id): 将id追加到builder中,并返回追加id后的builder。
2.2 UriMatcher
UriMatcher用于匹配Uri。它的用法如下:
(01) 创建UriMatcher对象。
(02) 把你需要匹配Uri路径通过addURI()注册到UriMatcher对象上。
(03) 注册成功后,ContentProvider就可以通过UriMatcher监听你注册的Uri。当有匹配的Uri动作(如插入)时,再就可以通过UriMatcher的match()函数来获取Uri的一个标识,该标识是在addURI()时传入的。这个标识的作用是方便在switch语句中对不同的Uri进行处理。
UriMatcher的主要API说明:
void addURI(String authority, String path, int code): 将"authority"+"path"注册的Uri注册到UriMatcher中,code是该Uri对应的标识。
int match(Uri uri): 匹配Uri,并返回Uri对应的标识。这里返回的标识与addURI中的标识对应。
ContentProvider
1.1ContentProvider简介
ContentProvider通常用于共享数据。
当其他程序需要访问本程序的数据,并且数据的结构比较复杂时,就可以使用ContentProvider来共享数据。如果数据不需要跨程序访问,使用数据库即可;如果数据结构比较简单,可以考虑前面提到的通过Intent共享文本等简单数据,或者通过FileProvider共享文件。
1.2ContentProvider示例
接下来,实现一个ContentProvider。该ContentProvider包括两部分:ContentProvider提供者APK 和 ContentProvider测试APK。
(01) ContentProvider提供者:自定义一个ContentProvider,并监听相应的URI。客户可以通过URI插入/删除/更新/查询数据。ContentProvider中的数据记录的是人的信息,包括"姓名,出生年月,email,性别"等信息。
(02) ContentProvider测试APK:通过URI向ContentProvider发起插入/删除/更新/查询等操作。
下面介绍ContentProvider的实现步骤。
2. ContentProvider提供者APK
2.1 ContentProvider的存储表格
根据ContentProvider的数据特性,我们建立一张表,表格包括"id/姓名/出生年月/email/性别"这些信息。表对应的类如下:
    public final class MyContract {
        public MyContract() {}
        /**
         * BaseColumns类中有两个属性:_ID 和 _COUNT
         */
        public static abstract class Entry implements BaseColumns {
            public static final String TABLE_NAME = "mytable01";
            public static final String NAME       = "name";
            public static final String BIRTH_DAY  = "birthday";
            public static final String EMAIL      = "email";
            public static final String GENDER     = "gender";
        }
    }
说明:BaseColumns是Android自带的类,它集成了"_ID"和"_COUNT"两个属性。
2.2 ContentProvider对应的manifest
在manifest中声明我们自定义的ContentProvider。
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
    <provider android:name="MyProvider" android:authorities="com.skw.myprovider" />
</application>
2.3 自定义的ContentProvider类
完成ContentProvider类,主要需要注意以下几点:
(01) ContentProvider的数据一般是以"数据库"或"网络数据"的方式存储的。如果是数据库,则需要实现SQLiteOpenHelper类。通过SQLiteOpenHelper类新建/管理数据库。
(02) ContentProvider主要是以Uri的形式方式访问的(也可以通过Intent)。要通过UriMatcher注册ContentProvider监听的Uri。
(03) ContentProvider是一个抽象类。当我们需要以继承ContentProvider的方式自定义ContentProvider时,需要实现query(), insert(), update(), delete(), getType(), onCreate()这六个函数。
2.3.1 数据库
下面,先介绍ContentProvider的数据库的实现。
    // 创建表格的SQL语句
    private static final String SQL_CREATE_ENTRIES =
        "CREATE TABLE " + Entry.TABLE_NAME + " (" +
        Entry._ID + " INTEGER PRIMARY KEY," +
        Entry.NAME + " TEXT NOT NULL, " +
        Entry.BIRTH_DAY + " TEXT, " +
        Entry.EMAIL + " TEXT, " +
        Entry.GENDER + " INTEGER " +
        " )";
    private class DBLiteHelper extends SQLiteOpenHelper {
        public static final int DATABASE_VERSION = 1;
        public static final String DATABASE_NAME = "MyProvider.db";
        public DBLiteHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(SQL_CREATE_ENTRIES);
        }
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }
3.3.2 注册Uri
    // Uri的authority
    public static final String AUTHORITY = "com.skw.myprovider";
    // Uri的path
    public static final String PATH = "table01";
    // UriMatcher中URI对应的序号
    public static final int ITEM_ALL = 1;
    public static final int ITEM_ID  = 2;
    private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        URI_MATCHER.addURI(AUTHORITY, PATH, ITEM_ALL);
        URI_MATCHER.addURI(AUTHORITY, PATH+"/#", ITEM_ID);
    }
说明:通过addURI()就可以将URI注册到UriMatcher中,从而实现ContentProvider对URI的监听。这里的AUTHORITY与manifest中的android:authorities一致!
例如, URI_MATCHER.addURI(AUTHORITY, PATH, ITEM_ALL); 意味着ContentProvider对"content://con.skw.myprovider/table01"进行监听。
例如, URI_MATCHER.addURI(AUTHORITY, PATH+"/#", ITEM_ALL); 意味着ContentProvider对"content://con.skw.myprovider/table01/5"进行监听。
3.3.2 实现ContentProvider的抽象函数
onCreate()
    @Override
    public boolean onCreate() {
        mDbHelper = new DBLiteHelper(this.getContext());
        Log.d(TAG, "open/create table");
        return true;
    }
说明:onCreate()中新建SQLiteOpenHelper对象。
delete()
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        int count = 0;
        switch (URI_MATCHER.match(uri)) {
        case ITEM_ALL:
            count = db.delete(Entry.TABLE_NAME, selection, selectionArgs);
            Log.d(TAG, "delete ITEM uri="+uri+", count="+count);
            break;
        case ITEM_ID:
            // 获取id列的值
            String id = uri.getPathSegments().get(1);
            count = db.delete(Entry.TABLE_NAME, Entry._ID+"=?", new String[]{id});
            Log.d(TAG, "delete ITEM_ID id="+id+", uri="+uri+", count="+count);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI"+uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }
说明:delete()中会通过match()获取uri对应的编码。这里的编码就是和addURI()注册uri的编码是相对应的。此外,notifyChange()的作用是通常数据库变化,若有ContentObserver监听该Uri,则notifyChange()最终会将消息传递给监听者。
insert(), update(), query()的实现与delete()类似,就不再说明。
getType()
    @Override
    public String getType(Uri uri) {
        switch (URI_MATCHER.match(uri)) {
        case ITEM_ALL:
            return "skw.myprovider.dir/table01";
        case ITEM_ID:
            return "skw.myprovider.item/table01";
        default:
            throw new IllegalArgumentException("Unknown URI"+uri);
        }
    }
说明:getType()是返回Uri对应的数据类型。
4. ContentProvider测试APK
    public class ProviderTest extends Activity
        implements View.OnClickListener {
        private static final String TAG = "##ProviderTest##";
        // 数据库的属性,与MyProvider的表格属性一致
        public static final String NAME      = "name";
        public static final String BIRTH_DAY = "birthday";
        public static final String EMAIL     = "email";
        public static final String GENDER    = "gender";
        // 数据库的URI
        public static final Uri CONTENT_URI = Uri.parse("content://com.skw.myprovider/table01");
        private ContentResolver mContentResolver = null;
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            ((Button)findViewById(R.id.insert)).setOnClickListener(this);
            ((Button)findViewById(R.id.deleteFirst)).setOnClickListener(this);
            ((Button)findViewById(R.id.deleteKate)).setOnClickListener(this);
            ((Button)findViewById(R.id.deleteAll)).setOnClickListener(this);
            ((Button)findViewById(R.id.update)).setOnClickListener(this);
            ((Button)findViewById(R.id.show)).setOnClickListener(this);
            // 删除第一行,然后全部打印出来
            mContentResolver = getContentResolver();
        }
        @Override
        public void onClick(View view) {
            switch(view.getId()) {
                case R.id.insert:
                    // 添加
                    insert("Jimmy", "20020201", "Jimmy20020201@126.com", 1);
                    insert("Kate",  "20030104", "kate20030104@126.com", 0);
                    insert("Li Lei", "20021124", "lilei20101124@126.com", 1);
                    insert("Lucy", "20010624", "lucy20101124@126.com", 0);
                    break;
                case R.id.deleteFirst:
                    ContentUris cus = new ContentUris();
                    Uri uri = cus.withAppendedId(CONTENT_URI, 1);
                    Log.d(TAG, "delete uri="+uri);
                    mContentResolver.delete(uri, null, null);
                    break;
                case R.id.deleteKate:
                    // 删除“username=Kate”的行,然后全部打印出来
                    mContentResolver.delete(CONTENT_URI, NAME+"=?", new String[]{"Kate"});
                    break;
                case R.id.deleteAll:
                    // 删除全部的行,然后全部打印出来
                    deleteAll() ;
                    break;
                case R.id.update:
                    // 更新第1个值,然后全部打印出来
                    updateItem() ;
                    break;
                case R.id.show:
                    // 打印全部的值
                    printAll() ;
                    break;
                default:
                    // 查找第2个值
                    //querySecondItem() ;
                    break;
            }
        }
        /*
         * 通过ContentResolver,将值插入到MyProvider中
         */
        private void insert(String name, String date, String email, int gender) {
            ContentResolver cr = getContentResolver();
            ContentValues cv = new ContentValues();
            cv.put(NAME, name);
            cv.put(BIRTH_DAY, date);
            cv.put(EMAIL, email);
            cv.put(GENDER, gender);
            Uri uri = cr.insert(CONTENT_URI, cv);
            Log.d(TAG, "insert uri="+uri);
        }
        private void updateItem() {
            ContentResolver cr = getContentResolver();
            ContentUris cus = new ContentUris();
            Uri uri = cus.withAppendedId(CONTENT_URI, 1);
            ContentValues cv = new ContentValues();
            cv.put(NAME, "update_name");
            cv.put(BIRTH_DAY, "update_date");
            cv.put(EMAIL, "update_email");
            cv.put(GENDER, 1);
            cr.update(uri, cv, null, null);
        }
        /*
         * 通过ContentResolver,将MyProvider中的值全部删除
         */
        private void deleteAll() {
            Log.d(TAG, "delete all value!");
            ContentResolver cr = getContentResolver();
            cr.delete(CONTENT_URI, null, null);
        }
        private void querySecondItem() {
            ContentResolver cr = getContentResolver();
            ContentUris cus = new ContentUris();
            Uri uri = cus.withAppendedId(CONTENT_URI, 2);
            String[] proj = new String[] { NAME, BIRTH_DAY, EMAIL, GENDER};
            Cursor cursor = cr.query(uri, proj, null, null, null);
            int index = 0;
            while (cursor.moveToNext()) {
                Log.d(TAG, "querySecondItem--"+index+"--"
                        +", email=" + cursor.getString(cursor.getColumnIndex(EMAIL))
                        +", username=" + cursor.getString(cursor.getColumnIndex(NAME))
                        +", date=" + cursor.getString(cursor.getColumnIndex(BIRTH_DAY))
                        +", gender=" + cursor.getInt(cursor.getColumnIndex(GENDER)));
                index++;
            }
        }
        private void printAll() {
            //通过contentResolver进行查找
            ContentResolver cr = getContentResolver();
            Log.d(TAG, "print all value!");
            // query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
            // 返回的列
            String[] proj = new String[] { NAME, BIRTH_DAY, EMAIL, GENDER};
            Cursor cursor = cr.query(
                CONTENT_URI, proj, null, null, null);
            int index = 0;
            while (cursor.moveToNext()) {
                Log.d(TAG, "printAll--"+index+"--"
                        +", email=" + cursor.getString(cursor.getColumnIndex(EMAIL))
                        +", username=" + cursor.getString(cursor.getColumnIndex(NAME))
                        +", date=" + cursor.getString(cursor.getColumnIndex(BIRTH_DAY))
                        +", gender=" + cursor.getString(cursor.getColumnIndex(GENDER)));
                index++;
            }
            startManagingCursor(cursor);  //查找后关闭游标
        }
    }
Permission介绍
    <permission
        android:name="com.skw.permission.myprovider"
        android:protectionLevel="normal"
        android:label="@string/permission_label"
        android:description="@string/permission_description"
        />
说明:permission常用的几个属性如下:
(01) android:name: 必需的。权限的名称,通常应遵循android 命名方案(.permission.)。
(02) android:protectionLevel: 必需的。权限的安全级别,共包括"normal, dangerous, signature, signatureOrSystem"四种。
normal 表示权限是低风险的,不会对系统、用户或其他应用程序造成危害;
dangerous 表示权限是高风险的,系统将可能要求用户输入相关信息,才会授予此权限;
signature 表示只有当应用程序所用数字签名与声明引权限的应用程序所用数字签名相同时,才能将权限授给它;
signatureOrSystem 表示将权限授给具有相同数字签名的应用程序或android 包类。
(03) android:permissionGroup: 非必需的。可以将权限放在一个组中,但对于自定义权限,应该避免设置此属性。
(04) android:label: 非必需的。标签。
(05) android:description: 非必需的。描述。
(06) android:icon非必需的。图标。
自定义Permission示例
1. 设置权限
    <application
        android:exported="true"
        android:label="@string/app_name"
        android:icon="@drawable/ic_launcher">
        <provider
            android:name="MyProvider"
            android:authorities="com.skw.myprovider"
            android:permission="com.skw.permission.myprovider">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </provider>
    </application>
    <permission
        android:name="com.skw.permission.myprovider"
        android:protectionLevel="normal"
        android:label="@string/permission_label"
        android:description="@string/permission_description"
        />
说明:上面是自定义权限的manifest文件内容。
(01) 首先,在需要定义权限的ContentProvider中声明权限android:permission="com.skw.permission.myprovider"
(02) 接着,再定义permission。permission对应的name与声明的权限对应。
2. 获取权限
在需要使用ContentProvider的APK的manifest中需要声明使用该权限。声明方法如下:
    <uses-permission android:name="com.skw.permission.myprovider" />
												
											[Android]Android四大组件之ContentProvider的更多相关文章
- Android实训案例(五)——四大组件之一ContentProvider的使用,通讯录的实现以及ListView的优化
		
Android实训案例(五)--四大组件之一ContentProvider的使用,通讯录的实现 Android四大组件是啥这里就不用多说了,看图吧,他们之间通过intent通讯 我们后续也会一一的为大 ...
 - Android 四大组件之" ContentProvider "
		
前言 ContentProvider作为Android的四大组件之一,是属于需要掌握的基础知识,可能在我们的应用中,对于Activity和Service这两个组件用的很常见,了解的也很多,但是对Con ...
 - Android四大组件之——ContentProvider(一)
		
Android四大组件之--ContentProvider(一) 本人邮箱:JohnTsai.Work@gmail.com,欢迎交流讨论. 欢迎转载,转载请注明网址:http://www.cnblog ...
 - 【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider
		
数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑.它只是负责为应用提供数据访问的接口.Android内置的许多数据都是使用ContentProvider形式,供 ...
 - Android四大组件之contentProvider
		
Activity,Service,broadcast and Contentprovider android 4 大组件. ContentProvider:使用: public class Image ...
 - Android的四大组件
		
Android的四大组件:Activity.Service.BroadcastReceiver.Content Provider. Content Provider 属于Android应用程序的组件之 ...
 - Android开发四大组件概述
		
这个文章主要是讲Android开发的四大组件,本文主要分为 一.Activity具体解释 二.Service具体解释 三.Broadcast Receiver具体解释 四.Content Provid ...
 - Android之四大组件、六大布局、五大存储 总结
		
Android之四大组件.六大布局.五大存储 一.四大组件:Android四大组件分别为activity.service.content provider.broadcast receiver. ...
 - Android的四大组件之Activity
		
Android的四大组件之Activity Activity:是Android组件中最基本也是最为常见用的四大组件(Activity,Service服务,Content Provider内容提供者,B ...
 - android中四大组件之间相互通信
		
好久没有写有关android有关的博客了,今天主要来谈一谈android中四大组件.首先,接触android的人,都应该知道android中有四大组件,activity,service,broadca ...
 
随机推荐
- BZOJ-3940:Censoring(AC自动机裸题)
			
Farmer John has purchased a subscription to Good Hooveskeeping magazine for his cows, so they have p ...
 - poj3784 Running Median[对顶堆]
			
由于我不会讲对顶堆,所以这里直接传上一个巨佬的学习笔记. 对顶堆其实还是很容易理解的,想这题的时候自己猜做法也能把没学过的对顶堆给想出来.后来了解,对顶堆主要还是动态的在线维护集合$K$大值.当然也可 ...
 - 洛谷P2024食物链——并查集补集的灵活运用
			
题目:https://www.luogu.org/problemnew/show/P2024 自己在做本题时最大的障碍就是:不会在一个集合的father改变时把相应的补集也跟着改变. 借鉴题解后,才明 ...
 - WPF ListView VisualPanel
			
<ItemsPanelTemplate x:Key="ItemsPanelTemplate1"> &l ...
 - JavaScript:bootstrap 模态框的简单应用
			
最近用上了bootstrap这个强大的前端框架,有空来总结一下.这里记录下模态框的简单应用. 首先,要在页面中引入相应的js.css文件 <link href="css/bootstr ...
 - Windows WMIC命令使用详解2
			
Windows WMIC命令使用详解(附实例) https://blog.csdn.net/aflyeaglenku/article/details/77878525 第一次执行WMIC命令时,Win ...
 - linux 遍历目录+文件(优化版本)
			
c++17 filesystem, regex 遍历目录 #include <stdio.h> #include <sys/types.h> #include <dire ...
 - 使用外网控制你的STM32单片机
			
转自:http://blog.csdn.net/xdxlove/article/details/50837459 本文章假设读者已经在STM32单片机上成功移植LWIP,且已经实现在局域网内控制STM ...
 - 1.14不使用回车键来读取n个字符
			
read是一个重要的bash命令,它用于从键盘或标准输入中读取文本.可以使用read以交互的形式读取来自用户的输入,不过read能做的远不止这些.很多编程语言的输入库都是从键盘读取输入,且只有回车键按 ...
 - Laravel中使用Session存取验证码信息
			
1.将验证码存储到session中. $request->session()->put('validate_code',$validateCode->getCode());//存储信 ...