Content Provider Test过程中遇到的坑

Content Provider(内容提供器)

一、什么是Content Provider?

  直接贴官方文档简介图,笔者太懒了,而且 坑 不在这?

 说白了,就是谷歌 对不同应用间的数据库共享 提供的一个安全的机制。(比如我们读取联系人列表,其实就是访问通讯录进程的数据库数据)

二、Content Provider应用角度?

  1.使用已经存在的Content Provider,进行访问 其他 进程的 数据,同上的读取联系人列表。

  2.创建一个Content Provider,来使自己的APP中的可供共享的数据被其他应用程序访问到。

三、Content Provider 的使用方法:

  Content Provider 的使用类似 SQLiteDatabases

  首先获取 ContentResolver 对象,可以使用getContentResolver()方法获取此对象,ContentResolver 对象提供了 6 个方法,分别是:

  1.onCreat() 2.insert() 3.delete() 4.select() 5.updata() 6.getType()

  这6个方法中,除了onCreat()之外的其他5个方法都是我们在调用方需要使用的,其中包括了常用的CRUD(增删查改)。

  如果我们要创建自己的Content Provider,就需要创建个实体类去 继承 ContentProvider 类 并 重写这6个方法。

  客户端应用进程中的 ContentResolver 对象和拥有提供程序的应用中的 ContentProvider 对象可自动处理跨进程通信。 ContentProvider 还可充当其数据存储区和表格形式的数据外部显示之间的抽象层。

  在讲解增删查改方法前,我们还需要了解 Uri 

  首先 要分析 一个完整的Uri由哪些部分组成

  一个完整的Uri由三部分构成,第一部分为协议类型(笔者自己起的名字),官方解释为 :scheme为:content://  是固定形式

  第二部分 Authoritis 对应 某一个具体的应用程序,通常用报名来标识,例如程序所在包为com.example.test.app那么对应的 的Authoritis就是com.example.test.app

  第三部分 Path ,对应的是 根据Authoritis指定的应用程序中区分不同的数据,比如要操作com.example.test.app程序中的表book,那么就可以在Authoritis的后面加上/book

  也就是说 一个完整的Uri应该满足这种形式:content://com.example.test.app/book

  或者也可以这样:content://com.example.test.app/book/11,这样表示要访问操作的是程序app中的book表中的id为11的数据

  那么,这是我们引进通配符的概念

  * 代表任意长度的任何字符

  # 代表任意长度的数字

  那么当我们想要访问一个程序中的任意表时:content://这里是程序 Authoritis /*

  当我们要访问一个程序中的某个表中的任意一条数据时:content://这里是程序 Authoritis/表名/#

  好了,讲了这么多,以query操作为例,演示一下,使用以存在的Content Provider(还是贴官方文档图)

  好了,基本的使用 就介绍到这里了,详情见官方文档:https://developer.android.com/guide/topics/providers/content-provider-basics.html?hl=zh-cn

四、我的测试过程遇到的坑

  笔者在一个之前做过的SQLiteDatabases程序中,新建了一个MyProvider类继承了ContentProvider类并重写了其6个方法,贴代码

  1 package com.example.lichangxin.sqlitetest;
  2 
  3 import android.content.ContentProvider;
  4 import android.content.ContentValues;
  5 import android.content.UriMatcher;
  6 import android.database.Cursor;
  7 import android.database.sqlite.SQLiteDatabase;
  8 import android.net.Uri;
  9 import android.support.annotation.NonNull;
 10 import android.support.annotation.Nullable;
 11 import android.util.Log;
 12 
 13 public class DatabasesProvider extends ContentProvider {
 14 
 15 
 16     public static final int Book_DIR = 1;
 17     public static final int BOOK_ITEM = 2;
 18 
 19     public static final String AUTHORITY = "com.example.lichangxin.sqlitetest.provider";
 20     public static UriMatcher uriMatcher;
 21     public MyDatabases myDatabases;
 22 
 23     static {
 24 
 25         uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 26         uriMatcher.addURI(AUTHORITY,"book",Book_DIR);
 27         uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
 28 
 29     }
 30 
 31     @Override
 32     public boolean onCreate() {
 33         //Log.d("enen","oncreate success!");
 34         myDatabases = new MyDatabases(getContext(),"waibu.db",null,2);
 35         if(myDatabases!=null){
 36             return true;
 37         }else {
 38             return false;
 39         }
 40     }
 41 
 42     @Nullable
 43     @Override
 44     public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
 45 
 46         Log.d("enen","insert 1");
 47         SQLiteDatabase sqLiteDatabase = myDatabases.getWritableDatabase();   // 定位 到这里出现问题
 48         Log.d("enen","insert 2");
 49         Uri urireturn = Uri.parse("content://" + AUTHORITY + "/book/" + 12);
 50         Log.d("enen","insert 3");
 51 
 52         switch (uriMatcher.match(uri)){    // switch未执行, 判断 uriMatcher()匹配uri 失败
 53             case BOOK_ITEM:
 54             case Book_DIR:
 55                 long return_id = sqLiteDatabase.insert("book",null,values);
 56                 urireturn = Uri.parse("content://" + AUTHORITY + "/book/" + return_id);
 57                 Log.d("enen","insert success");
 58                 break;
 59             case UriMatcher.NO_MATCH:
 60                 urireturn = Uri.parse("content://" + AUTHORITY + "/book/" + 66);
 61                 break;
 62             default:
 63                 break;
 64         }
 65 
 66         return urireturn;
 67 
 68     }
 69 
 70     @Override
 71     public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
 72         Log.d("enen","delete");
 73         SQLiteDatabase sqLiteDatabase = myDatabases.getWritableDatabase();
 74         int deleterows = 0;  // 删除的行数 用于返回值
 75         switch (uriMatcher.match(uri)){
 76             case BOOK_ITEM:
 77                 deleterows = sqLiteDatabase.delete("book",selection,selectionArgs);
 78                 break;
 79             case Book_DIR:
 80                 deleterows = sqLiteDatabase.delete("book",selection,selectionArgs);
 81                 break;
 82             default:
 83                 break;
 84         }
 85         return deleterows;
 86     }
 87 
 88     @Nullable
 89     @Override
 90     public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
 91         Log.d("enen","query");
 92         SQLiteDatabase sqLiteDatabase = myDatabases.getWritableDatabase();
 93         Cursor cursor = null;
 94         switch (uriMatcher.match(uri)){
 95             case BOOK_ITEM:
 96                 String bookid = uri.getPathSegments().get(1);
 97                 cursor = sqLiteDatabase.query("book",projection,"id=?",new String[]{bookid},null,null,sortOrder);
 98                 break;
 99             case Book_DIR:
100                 cursor = sqLiteDatabase.query("book",projection,selection,selectionArgs,null,null,sortOrder);
101                 break;
102             default:
103                 break;
104         }
105         return cursor;
106     }
107 
108     @Override
109     public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
110         Log.d("enen","updata");
111         SQLiteDatabase sqLiteDatabase = myDatabases.getReadableDatabase();
112         int updatarows = 0;
113         switch (uriMatcher.match(uri)){
114             case BOOK_ITEM:
115                 String updataid = uri.getPathSegments().get(1);
116                 updatarows = sqLiteDatabase.update("book",values,"id=?",new String[]{updataid});
117                 break;
118             case Book_DIR:
119                 updatarows = sqLiteDatabase.update("book",values,selection,selectionArgs);
120                 break;
121             default:
122                 break;
123         }
124 
125         return updatarows;
126     }
127 
128     @Nullable
129     @Override
130     public String getType(@NonNull Uri uri) {
131 
132         switch (uriMatcher.match(uri)){
133             case BOOK_ITEM:
134                 return "vnd.android.cursor.item/vnd." + AUTHORITY + ".book";
135             case Book_DIR:
136                 return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".book";
137             default:
138                 break;
139         }
140         return null;
141     }
142 }

再贴一份 数据库MyDatabases的,方便整体阅读:

 1 package com.example.lichangxin.sqlitetest;
 2 
 3 import android.content.Context;
 4 import android.database.sqlite.SQLiteDatabase;
 5 import android.database.sqlite.SQLiteOpenHelper;
 6 import android.widget.Toast;
 7 
 8 /**
 9  * Created by lichangxin on 2017/6/17.
10  */
11 
12 public class MyDatabases extends SQLiteOpenHelper {
13 
14     public static final String CREATE_BOOK = "create table book("
15             + "id integer primary key autoincrement,"
16             + "name text,"
17             + "author text,"
18             + "price real,"
19             + "pages integer)";
20 
21 //    public static final String CREATE_CATEGORY = "create table Category("
22 //            + "id integer primary key autoincrement,"
23 //            + "category_name text,"
24 //            + "enen text,"
25 //            + "category_code integer)";
26 
27     private Context mcontext;
28 
29     public MyDatabases(Context context,String name,SQLiteDatabase.CursorFactory factory,int version){
30         super(context,name,factory,version);
31         mcontext = context;
32     }
33 
34 
35     @Override
36     public void onCreate(SQLiteDatabase db) {
37 
38         db.execSQL(CREATE_BOOK);   // 建表语句, 这样可以保证数据库创建完成时的同时创建Book表
39 //        db.execSQL(CREATE_CATEGORY);
40 //        Toast.makeText(mcontext,"创建 成功",Toast.LENGTH_SHORT).show();
41 
42     }
43 
44     @Override
45     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
46 //        db.execSQL("drop table if exists book");
47 //        db.execSQL("drop table if exists Category");
48 //        onCreate(db);
49     }
50 }

完成以上的操作后,还要在AndroidManifest.xml中进行简单的编辑

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3           package="com.example.lichangxin.sqlitetest">
 4 
 5     <application
 6         android:allowBackup="true"
 7         android:icon="@mipmap/ic_launcher"
 8         android:label="@string/app_name"
 9         android:roundIcon="@mipmap/ic_launcher_round"
10         android:supportsRtl="true"
11         android:theme="@style/AppTheme">
12         <activity android:name=".MainActivity">
13             <intent-filter>
14                 <action android:name="android.intent.action.MAIN"/>
15 
16                 <category android:name="android.intent.category.LAUNCHER"/>
17             </intent-filter>
18         </activity>
19 
20         <provider
21             android:name=".DatabasesProvider"
22             android:authorities="com.example.lichangxin.sqlitetest.provider"
23             android:enabled="true"
24             android:exported="true">
25         </provider>
26 
27     </application>
28 
29 
30 </manifest>

可以看到 我们在<application>标签内 加入了<provider>元素

具体的关于 权限细节 和 <privider>元素 属性,下面贴几张官方文档截图

 

 

然后又新建了一个App,以下称此App为Provider_diaoyong;

在这边提供了四个按钮用来对应 操作 提供程序 的增删查改:

 1 package com.example.lichangxin.sqlite_provider_test_peihe;
 2 
 3 import android.content.ContentValues;
 4 import android.database.Cursor;
 5 import android.net.Uri;
 6 import android.support.v7.app.AppCompatActivity;
 7 import android.os.Bundle;
 8 import android.util.Log;
 9 import android.view.View;
10 import android.widget.Button;
11 import android.widget.Toast;
12 
13 public class MainActivity extends AppCompatActivity implements View.OnClickListener{
14 
15     private String newid = null;
16 
17     private static final String ABC = "com.example.lichangxin.sqlitetest.provider";
18 
19     @Override
20     protected void onCreate(Bundle savedInstanceState) {
21         super.onCreate(savedInstanceState);
22         setContentView(R.layout.activity_main);
23 
24         Button insert = (Button)findViewById(R.id.insert_button);
25         Button delete = (Button)findViewById(R.id.delete_button);
26         Button query = (Button)findViewById(R.id.query_button);
27         Button updata = (Button)findViewById(R.id.updata_button);
28 
29         insert.setOnClickListener(this);
30         delete.setOnClickListener(this);
31         query.setOnClickListener(this);
32         updata.setOnClickListener(this);
33 
34     }
35 
36     @Override
37     public void onClick(View v) {
38         switch (v.getId()){
39             case R.id.insert_button:
40                 //添加
41                 Log.d("enen","insert");
42                 Uri uri = Uri.parse("content://" + ABC + "/book");
43                 ContentValues values = new ContentValues();
44                 values.put("name","外部添加name");
45                 values.put("author","外部添加name");
46                 values.put("pages",999);
47                 values.put("price",999);
48                 Uri newuri = getContentResolver().insert(uri,values);
49                 
50                 if(newuri!=null){
51                     Log.d("enen","1111");
52                 }else{
53                     Log.d("enen","22222");   //说明 getContentResolver().insert(uri,values); 返回了一个null
54                 }
55                 
56                 try{
57                     newid = newuri.getPathSegments().get(1);
58                 }catch (NullPointerException e){
59                     Log.d("enen","异常-> " + e.getMessage());
60                 }
61                 
62                 Toast.makeText(this,"当前有" + newid + "个数据",Toast.LENGTH_SHORT).show();
63                 break;
64             case R.id.delete_button:
65                 //删除
66                 Uri uri1 = Uri.parse("content://" + ABC + "/book/" + newid);
67                 getContentResolver().delete(uri1,null,null);
68                 break;
69             case R.id.query_button:
70                 //查找
71                 Uri uri2 = Uri.parse("content://" + ABC + "/book");
72                 Cursor cursor = getContentResolver().query(uri2,null,null,null,null);
73                 if(cursor!=null){
74                     while(cursor.moveToNext()){
75                         String name = cursor.getString(cursor.getColumnIndex("name"));
76                         String author = cursor.getString(cursor.getColumnIndex("author"));
77                         int pages = cursor.getInt(cursor.getColumnIndex("pages"));
78                         int price = cursor.getInt(cursor.getColumnIndex("price"));
79                         Log.d("输出->name ",name);
80                         Log.d("输出->author ",author);
81                         Log.d("输出->pages"," " + pages);
82                         Log.d("输出->price"," " + price);
83                     }
84                     cursor.close();
85                 }
86                 break;
87             case R.id.updata_button:
88                 //修改
89                 break;
90             default:
91                 break;
92         }
93     }
94 }

因为是做测试 ,所以updata()的onClick事件的方法体没来及写。

好了,现在开始填坑,之前笔者在MyDatabases文件中的onCreat()方法中没有注释掉

Toast.makeText(mcontext,"创建 成功",Toast.LENGTH_SHORT).show();

此行代码,导致当我们点击 调用方的添加按钮时 程序执行到

Uri newuri = getContentResolver().insert(uri,values);

这步,然后会去调用我们 DatabasesProvider文件中的onCreat()方法,在这个onCreat()方法中 程序又会去创建一个 MyDatabases的实例。(如图)

紧接着程序流会转到DatabasesProvider的insert()方法中,在这个方法中 我们通过MyDatabases的实例对象的getWritableDatabases()方法获取到一个具体的数据库实例,在这个过程中 其实 又调用到了MyDatabases的onCreat()方法,贴一张此方法的图。

图中的mcontext就是MainActivity

 

 所以问题就出在这了,如果没有注释掉Toast的使用,那么Toast这条语句 就会 产生运行时异常(说说笔者的猜测,因为笔者想 当我们运行调用方的时候,提供程序所在的app运行在后台程序,那么对应的activity就是在停止状态onstop,那么此时的Toast 去使用 mcontext ,造成了运行时异常)。 勿喷?

注释掉这行代码之后,程序得以成功运行。

之前是因为 以为 Android Studio 只能运行一个project所以不能同时调试两个app,无意中看到一篇博客,可以 以New Window的形式打开多个project,然后 同时在两个程序中 使用日志输出,逐步定位到Bug所在行。

最后,教一下大家,怎么样 让Android Studio可以同时打开多个project(笔者只尝试过打开两个)。

找到Preferences->Appearance&Behavior->System Settings点进去,在旁边的参数设置窗口找到Project Opening选项

  选择Open project in new window。

最后的最后,记录一篇 关于外部存储和内部存储的好文章:http://www.jianshu.com/p/ad844547a43b

  

转载于:https://www.cnblogs.com/xykjlcx/p/7044443.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值