[Android学习笔记一] ContentProvider组件开发详解

  1. 云栖社区>
  2. 博客>
  3. 正文

[Android学习笔记一] ContentProvider组件开发详解

技术小阿哥 2017-11-14 13:54:00 浏览1216
展开阅读全文

   Android四大组件中ContentProvider组件相对Activity,BroadcastReceiver, Service而言比较独立,而且多数使用的时候都是在用Android系统提供的关于邮件,媒体,短信,联系人等ContentProvider。通常开发的应用较少提供ContentProvider组件,一般会在同家公司的产品或则内容适配方面会用到ContentProvider组件。

 

    本文主要讲述开发和使用ContentProvider组件的通用方式。其中包含:代码模板,权限设置,ContentResolver 等。


1 . 开发ContentProvider基本思路


   1.1.ContentProvider组件需要对外开放的内容

       授权字符串(Authoritis),内容类型,数据字段名称, 访问权限说明等


   1.2. 编写ContentProvider组件类继承ContentProvider类,实现其中的CRUD操作方法


   1.3. AndroidMainifest.xml中配置<provider>元素,指定控制属性,其中包含是否对外部应用可用,读写权限,Authorities字符串等


   1.4. 应用本身或者外部应用使用ContentProvider组件


2. 开发一个基于SQLite数据库提供Note表信息的ContentProvider


  2.1  开发一个独立的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
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
package secondriver.xprovider;
 
import android.content.*;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
/**
 * Author : secondriver
 * Date :  2015/11/4
 */
public class XProvider extends ContentProvider {
 
    private static final String TAG = "XProvider";
 
    //授权字符串
     
    //授权Uri
     
 
    //Note公开信息
    public static final class Note implements BaseColumns {
 
        //Note表名
         
        //Note表的内容Uri
       
    //内容类型
  
        //Note表字段
       
        //Uri匹配码  
 
    }
 
    //Uri匹配器
    public static UriMatcher uriMatcher;
 
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //添加Uri匹配内容
    }
 
    private XSQLiteHelper helper;
 
    @Override
    public boolean onCreate() {
        //ContentProvider组件创建时做的工作
        return true;
    }
 
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return cursor;
    }
 
    @Override
    public String getType(Uri uri) {
        return null;
    }
 
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }
 
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return effectRows;
    }
 
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return effectRows;
    }
 
    public static class XSQLiteHelper extends SQLiteOpenHelper {
 
        public static final String DATA_BASE_NAME = "xprovider.sqlite";
        public static final int DATA_BASE_VERSION = 1;
 
        public static volatile XSQLiteHelper xsqLiteHelper;
 
        public static XSQLiteHelper getXsqLiteHelper(Context context) {
            //实例化XSQLiteHelp对象
            return xsqLiteHelper;
        }
 
        public XSQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }
 
        @Override
        public void onCreate(SQLiteDatabase db) {
            //数据库初始化
        }
 
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 
        }
    }
}


      上面代码提供了一个SQLiteOpenHelper的实现,实际开发中可个会更具具体应用来提供给ContentProvider组件。

     

      中文注释部分可能需要填充具体代码,比如权限可以更具实际情况来定义,表字段根据要提供的内容信息来公开字段名并且需要在文档中说明。


   2.1 准备Note表信息

     

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
//Note公开信息
    public static final class Note implements BaseColumns {
 
        //Note表名
        public static final String TABLE_NAME = "note";
 
        //Note表的内容Uri
        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "note");
 
        //建议使用格式如: type/subtype => vnd.android.cursor.dir/vnd.<name>.<type>  name=package name  type=table name
        public static final String CONTENT_DIR_TYPE = "vnd.android.cursor.dir/vnd.secondriver.xprovider.note";
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.secondriver.xprovider.note";
 
        //ID主键
        public static final String ID_COLUMN = _ID;
        //内容列
        public static final String CONTENT_COLUMN = "CONTENT";
        //创建时间列表
        public static final String CREATED_COLUMN = "CREATED";
        //标识列
        public static final String FLAG_COLUMN = "FLAG";
        //状态列
        public static final String STATUS_COLUMN = "STATUS";
 
        //Uri匹配码
        private static final int NOTE_ITEM = 0x21;
        private static final int NOTE_DIR = 0x22;
 
    }


    说明:Uri匹配码声明为Note类的常量这样可以方便对照,一个ContentProvider组件中声明多个表公开类,比如User类,这样就比较容易区分操作的是那个表的内容。


 2.2  Note类外其它的具体代码


 

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package secondriver.xprovider;
 
import android.content.*;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
/**
 * Author : secondriver
 * Date :  2015/11/4
 */
public class XProvider extends ContentProvider {
 
    private static final String TAG = "XProvider";
 
    //授权字符串
    public static final String AUTHORITY = "secondriver.xprovider.X_PROVIDER";
    //授权Uri
    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
 
 
    //Uri匹配器
    public static UriMatcher uriMatcher;
 
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "note/#", Note.NOTE_ITEM);
        uriMatcher.addURI(AUTHORITY, "note", Note.NOTE_DIR);
    }
 
    private XSQLiteHelper helper;
 
    @Override
    public boolean onCreate() {
        helper = XSQLiteHelper.getXsqLiteHelper(getContext());
        return true;
    }
 
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                break;
            case Note.NOTE_ITEM:
                builder.appendWhere(Note.ID_COLUMN + "=" + uri.getPathSegments().get(1));
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
        builder.setTables(Note.TABLE_NAME);
        Cursor cursor = builder.query(helper.getReadableDatabase(), projection, selection, selectionArgs, null,
                null, sortOrder);
        return cursor;
    }
 
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_ITEM:
                return Note.CONTENT_ITEM_TYPE;
            case Note.NOTE_DIR:
                return Note.CONTENT_DIR_TYPE;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
    }
 
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = helper.getWritableDatabase();
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                long noteId = db.insert(Note.TABLE_NAME, null, values);
                if (noteId > 0) {
                    //插入成功
                    Uri newUri = ContentUris.withAppendedId(uri, noteId);
                    getContext().getContentResolver().notifyChange(newUri, null);
                    return newUri;
                else {
                    //插入失败
                    return null;
                }
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
    }
 
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = helper.getWritableDatabase();
        int effectRows = 0;
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                effectRows = db.delete(Note.TABLE_NAME, selection, selectionArgs);
                break;
            case Note.NOTE_ITEM:
                long id = ContentUris.parseId(uri);
                String whereClause = Note.ID_COLUMN + "=" + String.valueOf(id);
                if (!TextUtils.isEmpty(selection)) {
                    whereClause = whereClause + " AND " + selection;
                }
                effectRows = db.delete(Note.TABLE_NAME, whereClause, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return effectRows;
    }
 
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = helper.getWritableDatabase();
        int effectRows = 0;
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                effectRows = db.update(Note.TABLE_NAME, values, selection, selectionArgs);
                break;
            case Note.NOTE_ITEM:
                long nodeId = ContentUris.parseId(uri);
                String whereClause = Note.ID_COLUMN + "=" + String.valueOf(nodeId);
                if (!TextUtils.isEmpty(selection)) {
                    whereClause = whereClause + " AND " + selection;
                }
                effectRows = db.update(Note.TABLE_NAME, values, whereClause, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
        return effectRows;
    }
 
    public static class XSQLiteHelper extends SQLiteOpenHelper {
 
        public static final String DATA_BASE_NAME = "xprovider.sqlite";
        public static final int DATA_BASE_VERSION = 1;
 
        public static volatile XSQLiteHelper xsqLiteHelper;
 
        public static XSQLiteHelper getXsqLiteHelper(Context context) {
            if (null == xsqLiteHelper) {
                synchronized (XSQLiteHelper.class) {
                    if (null == xsqLiteHelper) {
                        xsqLiteHelper = new XSQLiteHelper(context, DATA_BASE_NAME, null, DATA_BASE_VERSION);
                    }
                }
            }
            return xsqLiteHelper;
        }
 
        public XSQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }
 
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.beginTransaction();
            try {
                db.execSQL(
                        new StringBuilder("create table if not exists ")
                                .append(Note.TABLE_NAME)
                                .append("(")
                                .append(Note.ID_COLUMN)
                                .append(" integer primary key autoincrement, ")
                                .append(Note.CONTENT_COLUMN)
                                .append(" varchar, ")
                                .append(Note.CREATED_COLUMN)
                                .append(" varchar,")
                                .append(Note.FLAG_COLUMN)
                                .append(" varchar,")
                                .append(Note.STATUS_COLUMN)
                                .append(" varchar")
                                .append(")")
                                .toString()
                );
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                for (int i = 0; i < 50; i++) {
                    ContentValues cv = new ContentValues();
                    cv.put(Note.CONTENT_COLUMN, "Note 内容 " + i);
                    cv.put(Note.CREATED_COLUMN, simpleDateFormat.format(new Date()));
                    cv.put(Note.FLAG_COLUMN, i);
                    cv.put(Note.STATUS_COLUMN, i);
                    db.insert(Note.TABLE_NAME, null, cv);
                }
                db.setTransactionSuccessful();
            catch (SQLiteException e) {
                Log.e(TAG, e.getMessage());
            finally {
                db.endTransaction();
            }
        }
 
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 
        }
    }
}


    

    3. 在AndroidMainfest.xml清单文件中声明ContentProvider组件


     3.1  声明XProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--
            按如下顺序验证权限
            android:grantUriPermssions: Temporary permission flag.
            android:permission: Single provider-wide read/write permission.
            android:readPermission: Provider-wide read permission.
            android:writePermission: Provider-wide write permission.
        -->
        <provider
                android:permission="secondriver.xprovider.permission.X_PROVIDER"
                android:authorities="secondriver.xprovider.X_PROVIDER"
                android:name=".XProvider"
                android:exported="true">
            <!--
                子元素:
                grant-uri-permission :Uri临时访问授权
                path-permission :Uri细粒度控制读写权限
            -->
        </provider>


    3.2 定义权限

         清单文件中声明XProvider的时候使用到了内容读写权限

         “secondriver.xprovider.permission.X_PROVIDER” 因此该权限需要清单文件中定义。

        

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<permission
            android:name="secondriver.xprovider.permission.X_PROVIDER"
            android:label="xProvider的读写权限"
            android:description="@string/permission_x_provider_desc"
            android:protectionLevel="normal"/>
   <!--
    <permission
            android:name="secondriver.xprovider.permission.READ_X_PROVIDER"
            android:label="xProvider的读权限"
            android:description="@string/permission_read_x_provider_desc"
            android:protectionLevel="normal"/>
    <permission
            android:name="secondriver.xprovider.permission.WRITE_X_PROVIDER"
            android:label="xProvider的写权限"
            android:description="@string/permission_write_x_provider_desc"
            android:protectionLevel="normal"/> 
    -->


     权限定义时label属性的值通常为”XXX的权限“,description属性的值通常是”允许该应用干什么,授权有XXX的危害“。比如:label =发送持久广播权限 description=允许该应用发送持久广播,此类消息在广播结束后仍会保留。过多使用会占用手机过多内容,从而降低其速度或稳定性。 


        3.3 权限说明

        默认情况下Provider是没有权限控制的,因此一旦exported=true,那么外部其它应用都可以访问到Provider提供的内容,为了更加安全,有效,范围合适的控制需要添加权限控制。

        Provider权限分为: 

  •  读写的Provider层权限

  •  读写分开的Provider层权限

  •  Path层权限

  •  临时授权

       四种权限从上往下优先级越高。


      Path层权限:是对于Uri的更具细粒度的权限控制,provider元素的子元素中可以配置grant-ui-permission和path-permission 。

      

      临时授权:provider元素属性grantUriPermissions=true时系统将授予应用临时权限访问完整的Provider,覆盖掉Provider和Path层的权限;grantUriPermissions=false时需要在provider元素的子元素中配置一个或者多个grant-uri-permission元素来为指定的Uri的临时访问授权。

      另外应用在使用临时授权访问Provider时Provider应用会在返回的Intent中通过的setFlags方法指定FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION标识,携带一个具有临时授权的Uri供外部应用完成本次内容访问操作。


  4. 在本应用和其它应用中使用ContentProvider提供的内容


      这里提供在其它应用中使用ContentProvider。在使用外部提供的ContentProvider通常需要了解的内容便是文字1.1部分提到。


    4.1 下面是通过一个ListView来展示Note中的”CONTENT“和”CREATED“字段信息


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
package secondriver.oapp;
 
import android.app.Activity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 * Author : secondriver
 * Date :  2015/11/5
 */
public class XResolverActivity extends Activity {
 
    private SimpleAdapter mAdapter;
    private ListView mListView;
    private List<Map<String, String>> mData;
 
    private final int xproviderLoad = 0x01;
    private LoaderManager loaderManager;
    private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderManager.LoaderCallbacks<Cursor>() {
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            CursorLoader cursorLoader = new CursorLoader(getApplicationContext(),
                    Uri.parse("content://secondriver.xprovider.X_PROVIDER/note"),
                    new String[]{
                            "_id",
                            "CONTENT",
                            "CREATED"
                    }, nullnullnull);
            return cursorLoader;
        }
 
        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            int idIndex = data.getColumnIndexOrThrow("_id");
            int contentIndex = data.getColumnIndexOrThrow("CONTENT");
            int createdIndex = data.getColumnIndexOrThrow("CREATED");
            mData.clear();
            while (data.moveToNext()) {
                HashMap<String, String> m = new HashMap<>();
                m.put("CONTENT", data.getString(contentIndex));
                m.put("CREATED", data.getString(createdIndex));
                m.put("_id", data.getString(idIndex));
                mData.add(m);
            }
            mAdapter.notifyDataSetChanged();
        }
 
        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
 
        }
    };
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xresolver);
 
        mListView = (ListView) findViewById(android.R.id.list);
        mData = new ArrayList<>();
        mAdapter = new SimpleAdapter(this, mData, android.R.layout.simple_list_item_2,
                new String[]{
                        "CONTENT",
                        "CREATED"
                },
                new int[]{
                        android.R.id.text1,
                        android.R.id.text2
                });
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                HashMap m = (HashMap) mData.get(position);
                String noteId = (String) m.get("_id");
                String noteContent = (String) m.get("CONTENT");
                getContentResolver().delete(Uri.withAppendedPath(Uri.parse("content://secondriver.xprovider.X_PROVIDER/note"), noteId), nullnull);
                mData.remove(position);
                mAdapter.notifyDataSetChanged();
                Toast.makeText(XResolverActivity.this"Delete :" + noteContent, Toast.LENGTH_LONG).show();
            }
        });
 
        loaderManager = getLoaderManager();
        loaderManager.initLoader(xproviderLoad, new Bundle(), callbacks);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        if (null != loaderManager) {
            loaderManager.restartLoader(xproviderLoad, new Bundle(), callbacks);
        }
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != loaderManager) {
            loaderManager.destroyLoader(xproviderLoad);
        }
    }
}


     说明:需要额外注意的是代码中的硬编码字符串内容,这些内容正是XProvider类和Note类公开的信息,这些内容通常在ContentProvider组件的使用文档中公开说明的。如果是在应用内部使用XProvider的话,那么就可以直接使用变量名而避免硬编码。如下代码片段所示:

 

1
2
3
4
5
6
7
8
9
 mAdapter = new SimpleAdapter(this, mData, android.R.layout.simple_list_item_2,
                new String[]{
                        XProvider.Note.CONTENT_COLUMN,
                        XProvider.Note.CREATED_COLUMN
                },
                new int[]{
                        android.R.id.text1,
                        android.R.id.text2
                });


   由于XProvider的访问需要读写权限,因此需要在清单文件中声明使用的权限。

   

1
<uses-permission android:name="secondriver.xprovider.permission.X_PROVIDER"/>


  5. ContentProvider组件小结

     在开发ContentProvider时尽可能使其具备以下特点:

     提供恰当的内容访问范围;ContentProvider组件独立封装;详细的权限,Uri,提供内容的使用说明。



本文转自 secondriver 51CTO博客,原文链接:http://blog.51cto.com/aiilive/1710151,如需转载请自行联系原作者

网友评论

登录后评论
0/500
评论
技术小阿哥
+ 关注