Android之通过ContentProvider实现两个app(进程间)间通信以及函数调用

1、ContentProvider简单介绍

ContentProvider以在不同的应用程序之间共享数据,ContentProvider底层实现是Binder,它为存储和获取数据提供统一的接口





2、实现哪些功能?

比如我们有两个app,分别是ContentProviderServer和ContentProviderClient
1)、需要在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,然后涉及到安全访问数据,我们可以把签名信息作为唯一身份去校验。

猜你喜欢

转载自blog.csdn.net/u011068702/article/details/80820985