1、ContentProvider简单介绍
ContentProvider以在不同的应用程序之间共享数据,ContentProvider底层实现是Binder,它为存储和获取数据提供统一的接口2、实现哪些功能?
比如我们有两个app,分别是ContentProviderServer和ContentProviderClient1)、需要在app里面ContentProviderServer创建自己的数据库,然后提供接口,让ContentProviderClient这个app,去查询和插入数据
到ContentProviderServer里面的数据库。
2)、让ContentProviderClient调用ContentProviderServer里面的函数,得到我们ContentProviderServer里面的bundle里面携带的数据
3、Demo实现
在ContentProviderServer这个app中步骤如下1)、在ContentProviderServer中创建数据库,这里是DbOpenHelper.java类文件,这里创建了学生数据库
package com.example.contentprovidertest;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DbOpenHelper extends SQLiteOpenHelper {
public static final String TAG = "DbOpenHelper";
private static final String DB_NAME = "student_provider.db";
public static final String STUDENT_TABLE_NAME = "student";
private static final int DB_VERSION=1;
private String mCreateTable="create table if not exists " + STUDENT_TABLE_NAME +"(id integer primary key," + "name TEXT, "+"sex TEXT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.d(TAG, "DbOpenHelper onCreate");
db.execSQL(mCreateTable);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2)、实现ContentProvider,这里是StudentProvider.java类文件
package com.example.contentprovidertest;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
public class StudentProvider extends ContentProvider {
public static final String TAG = "StudentProvider";
public static final String AUTHORITY = "com.example.contentprovidertest.StudentProvider";
private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private SQLiteDatabase mDb;
private Context mContext;
private String mTable;
@Override
public Bundle call(String method, String arg, Bundle extras) {
Log.d(TAG, "StudentProvider call start......");
Log.d(TAG, "StudentProvider call method is:" + method);
Bundle bundle = new Bundle();
//这里传递一个字符串,如果是在项目的话,传递你需要在这个项目中得到的数据放在bundle里面去
//这里只是测试,我放的字符串
bundle.putString(method, "chenyu");
return bundle;
}
static {
Log.d(TAG, "StudentProvider static start......");
mUriMatcher.addURI(AUTHORITY, "student", 0);
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
return 0;
}
@Override
public String getType(Uri arg0) {
return null;
}
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
Log.d(TAG, "StudentProvider insert");
mDb.insert(mTable, null, arg1);
mContext.getContentResolver().notifyChange(arg0, null);
return null;
}
@Override
public boolean onCreate() {
Log.d(TAG, "StudentProvider onCreate");
mTable = DbOpenHelper.STUDENT_TABLE_NAME;
//注意ContentProvider的onCreate在Application的onCreate的前面,可以查看系统源码
mContext = getContext();
initProvoder();
return false;
}
private void initProvoder() {
mDb = new DbOpenHelper(mContext).getWritableDatabase();
}
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
Log.d(TAG, "StudentProvider query");
String table = DbOpenHelper.STUDENT_TABLE_NAME;
Cursor mCursor = mDb.query(table, arg1, arg2, arg3, null, arg4, null);
return mCursor;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
return 0;
}
}
由于这里继承了ContentProvider,所以需要重写一些方法,我们在onCreate方法里面进行了数据库的初始化,然后创建了UriMatcher,在静态块里面增加了addURI了,然后实现了插入和查询方法,插入和查询方法是基于数据库操作的
,但是有时候我们需要调用ContentProviderServer里面的方法,那么我们需要重写call方法,最后返回的bundle可以携带这个app里面的数据传递给ContentProviderClient,至于如何调用,下面会介绍,既然有了ContentProvider,我们应该在AndroidManifest.xml里面声明provider,但是要记得加上authorities,这是可以理解为授权,是两个app通信的票据,声明如下。
<provider
android:authorities="com.example.contentprovidertest.StudentProvider"
android:name="com.example.contentprovidertest.StudentProvider"
android:process=":provider"
android:exported="true"
>
</provider>
如果是同一个app里面不同进程通信,可以不加android:exported="true",如果是不同app之间进程通信,一定要记得加android:exported="true",不加的话ContentProviderClient运行会提示下面的错误日志
E Caused by: java.lang.SecurityException: Permission Denial: opening provider com.example.contentprovidertest.GameProvider from ProcessRecord{bd01d13 13866:com.example.contentpro
viderclient/u0a99} (pid=13866, uid=10099) that is not exported from uid 10098
3)、实现Application,这里为什么要实现这个,就是想提醒读者,如果项目启动的时候就启动了ContentProvider,那么调用顺序一定是Application里面的attachBaseContext(final Context base)方法->ContentProvider里面的onCreate()方法->Application里面的onCreate()方法,后面结果日志打印可以看出,MyApplication.java文件文件如下
package com.example.contentprovidertest;
import android.app.Application;
import android.content.Context;
import android.util.Log;
public class MyApplication extends Application {
public static final String TAG = "MyApplication";
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
Log.d(TAG, "MyApplication attachBaseContext");
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "MyApplication onCreate");
}
}
4)、然后就是一个MainActivity.java文件,这里没啥好说的
package com.example.contentprovidertest;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
public class MainActivity extends ActionBarActivity {
public static final String TAG = "ContentProviderServer";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_main);
}
}
在ContentProviderClient这个app中步骤如下
1)、新建立一个Student.java类文件如下
package com.example.contentproviderclient;
import android.os.Parcel;
import android.os.Parcelable;
public class Student implements Parcelable {
public String mName;
public String mSex;
public Student(String name, String sex) {
this.mName = name;
this.mSex = sex;
}
protected Student(Parcel in) {
mName = in.readString();
mSex = in.readString();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeString(mSex);
}
}
2)、然后在MainActivity.java文件通过ContentProvider实现调用ContentProviderServer里面的插入数据和查询数据库的操作,同时调用ContentProviderServer里面的方法,返回携带ContentProviderServer里面数据的bundle回来,然后解开bundle就行,代码如下
package com.example.contentproviderclient;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
public class MainActivity extends ActionBarActivity {
public static final String TAG = "ContentProviderClient";
public static final String URI_STRING = "content://com.example.contentprovidertest.StudentProvider";
public static final String ID = "id";
public static final String NAME = "name";
public static final String SEX = "sex";
public static final String METHOD = "contentProviderClientMethod";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "MainActivity onCreate");
Uri uri = Uri.parse(URI_STRING);
ContentValues mContentValues = new ContentValues();
mContentValues.put(ID, 34);
mContentValues.put(NAME, "陈紫曦");
mContentValues.put(SEX, "女");
//插入数据到ContentProviderServer的数据库
getContentResolver().insert(uri, mContentValues);
//查询ContentProviderServer的数据库
Cursor cursor = getContentResolver().query(uri, new String[]{NAME, SEX}, null, null, null);
while (cursor.moveToNext()) {
Student mGame = new Student(cursor.getString(0), cursor.getString(1));
Log.d(TAG, "名字:" + mGame.mName + " 性别是:" + mGame.mSex);
}
//调用ContentProviderServer里面StudentProvider的call方法,可以简单得到ContentProviderServer中携带想要的数据的bundle过来
Bundle bundle = getContentResolver().call(uri, METHOD, null, null);
Log.d(TAG, "调用ContentProviderServer里面的方法得到的结果是:" + bundle.getString(METHOD));
}
}
很明显我们这里是得到一个uri,然后调用getContentResolver()的查询和插入方法来与ContentProviderServer通信,这里还有还调用了它的call方法,得到了携带ContentProviderServer里面数据的bundle过来
4、运行结果和分析
1)、启动ContentProviderServer得到ContentProviderServer打印的日志如下
MyApplication D MyApplication attachBaseContext
D MyApplication onCreate
2)、启动ContentProviderClient
ContentProviderServer打印的日志如下
MyApplication D MyApplication attachBaseContext
StudentProvider D StudentProvider static start......
D StudentProvider onCreate
MyApplication D MyApplication onCreate
StudentProvider D StudentProvider insert
D StudentProvider query
D StudentProvider call start......
D StudentProvider call method is:contentProviderClientMethod
ContentProviderClient打印的日志如下
ContentProviderClient D MainActivity onCreate
D 名字:陈紫曦 性别是:女
D 调用ContentProviderServer里面的方法得到的结果是:chenyu
我们可以看到启动ContentProviderServer的时候调用了一次MyApplication里面的attachBaseContext和onCreate方法,然后我们启动ContentProviderClient的时候,我们发现ContentProviderServer日志打印依然又调用了MyApplication的attachBaseContext和onCreate方法,只不过这个时候我们可以发现StudentProvider的onCreate方法夹在MyApplication的attachBaseContext和onCreate方法中间,也就是Application和ContextProvider都起来的时候,ContextProvider的onCreate方法一定是在MyApplication的attachBaseContext方法之后,在MyApplication的onCreate方法之前,读者可以分析ContextProvider启动源码,然后这里后面我们发现通过ContentProvider调用了StudentProvider里面的insert和query方法,然后也调用了里面的call方法,方法名是ContentProviderClient传递过去的方法名,然后ContentProviderClient日志打印也得到了相应的数据库里面的数据和携带ContentProviderServer数据的bundle.
5、总结
1、当一个app需要得操作另外一个app的数据库时候,一般使用ContentProvider,然后重写ContentProvider里面的增删改查方法。
2、当一个app需要调用另外一个app的方法,然后得到另外一个app的数据时候,也用ContentProvider,重写里面的call方法就行。
3、不同app进程通信一定要先考虑ContentProvider,然后涉及到安全访问数据,我们可以把签名信息作为唯一身份去校验。