由于应用程序之间不能共享内存。在不同应用程序之间交互数据(跨进程通讯),在android SDK中提供了4种用于跨进程通讯的方式。这4种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。其中Activity可以跨进程调用其他应用程序的Activity;Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;Service和Content Provider类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。
一、Activity
Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的 Class对象,而需要指定的是要访问的Activity所对应的Action(一个字符串)。有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。 在android系统中有很多应用程序提供了可以跨进程访问的Activity,例如,下面的代码可以直接调用拨打电话的Activity。如调用系统通话应用
Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:12345678" );
startActivity(callIntent);
二、 Brocast(广播)
广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。
在应用程序中发送广播比较简单。只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。如显示系统时间
三、 ContentProvider
ContentProvider向我们提供了我们在应用程序之前共享数据的一种机制,而我们知道每一个应用程序都是运行在不同的应用程序的,数据和文件在不同应用程序之间达到数据的共享不是没有可能,而是显得比较复杂,而正好Android中的ContentProvider则达到了这一需求,比如有时候我们需要操作手机里的联系人,手机里的多媒体等一些信息,我们都可以用到这个ContentProvider来达到我们所需。如访问系统相册
实例讲解:利用ContentProvider实现进程间的通信:
1、建立数据库,“game_provider.db”并创建一下表“game”:
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME="game_provider.db";
static final String GAME_TABLE_NAME="game";
private static final int DB_VERSION=1;
private String CREATE_GAME_TABLE="create table if not exists " + GAME_TABLE_NAME +"(_id integer primary key," + "name TEXT, "+"describe TEXT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_GAME_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2、使用ContentProvider对数据库进行操作
public class GameProvider extends ContentProvider {
public static final String AUTHORITY = "com.example.liuwangshu.mooncontentprovide.GameProvider";
public static final Uri GAME_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/game");
private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private SQLiteDatabase mDb;
private Context mContext;
private String table;
static {
mUriMatcher.addURI(AUTHORITY, "game", 0);
}
@Override
public boolean onCreate() {
table = DbOpenHelper.GAME_TABLE_NAME;
mContext = getContext();
initProvoder();
return false;
}
private void initProvoder() {
mDb = new DbOpenHelper(mContext).getWritableDatabase();
new Thread(new Runnable() {
@Override
public void run() {
mDb.execSQL("delete from " + DbOpenHelper.GAME_TABLE_NAME);
mDb.execSQL("insert into game values(1,'九阴真经ol','最好玩的武侠网游');");
}
}).start();
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String table = DbOpenHelper.GAME_TABLE_NAME;
Cursor mCursor = mDb.query(table, projection, selection, selectionArgs, null, sortOrder, null);
return mCursor;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
mDb.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
其中我们展开讲一下UriMatcher,UriMatcher本质上是一个文本过滤器,用在contentProvider中帮助我们过滤,分辨出查询者想要查询哪个数据表。
demo:
UriMatcher类用于匹配Uri,它的用法如下:
首先第一步把你需要匹配Uri路径全部给注册上,如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://cn.xxt.provider.personprovider/person路径,返回匹配码为1
sMatcher.addURI(“cn.xxt.provider.personprovider”, “person”, 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配content://cn.xxt.provider.personprovider/person/230路径,返回匹配码为2
sMatcher.addURI(“cn.xxt.provider.personprovider”, “person/#”, 2);//#号为通配符
switch (sMatcher.match(Uri.parse("content://cn.xxt.provider.personprovider/person/10"))) {
case 1
break;
case 2
break;
default://不匹配
break;
}
注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://cn.xxt.provider.personprovider/person路径,返回的匹配码为1
开启多进程的方法是在AndroidManifest.xml中这样声明:
<provider android:authorities="com.example.liuwangshu.mooncontentprovide. GameProvider"
android:name=".GameProvider"
android:process=":provider">
</provider>
3、在ContentProviderActivity中调用另一个进程GameProvider的方法,这一步就是进行进程间通信的重要一步
public class ContentProviderActivity extends AppCompatActivity {
private final static String TAG = "ContentProviderActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_provider);
Uri uri = Uri.parse("content://com.example.liuwangshu.mooncontentprovide.GameProvider");
ContentValues mContentValues = new ContentValues();
mContentValues.put("_id", 2);
mContentValues.put("name", "大航海时代ol");
mContentValues.put("describe", "最好玩的航海网游");
//在这里就调用了getContentResolver方法拿到resolver对象的insert方法,实际上是调用了GameResolver的insert方法
getContentResolver().insert(uri, mContentValues);
Cursor gameCursor = getContentResolver().query(uri, new String[]{"name", "describe"}, null, null, null);
while (gameCursor.moveToNext()) {
Game mGame = new Game(gameCursor.getString(0), gameCursor.getString(1));
Log.i(TAG, mGame.gameName + "---" + mGame.gameDescribe);
}
}
}
其中Game.java如下,实现Parcelable接口:
public class Game implements Parcelable {
public String gameName;
public String gameDescribe;
public Game(String gameName, String gameDescribe) {
this.gameName = gameName;
this.gameDescribe = gameDescribe;
}
protected Game(Parcel in) {
gameName = in.readString();
gameDescribe = in.readString();
}
public static final Creator<Game> CREATOR = new Creator<Game>() {
@Override
public Game createFromParcel(Parcel in) {
return new Game(in);
}
@Override
public Game[] newArray(int size) {
return new Game[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(gameName);
dest.writeString(gameDescribe);
}
}
查看打印日志:
四、 AIDL Service
AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写,对于小白来说,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。
它相比Broadcast而言,虽然实现上稍微麻烦了一点,但是它的优势就是不会像广播那样在手机中的广播较多时会有明显的时延,甚至有广播发送不成功的情况出现。 注意普通的Service并不能实现跨进程操作,实际上普通的Service和它所在的应用处于同一个进程中,而且它也不会专门开一条新的线程,因此如果在普通的Service中实现在耗时的任务,需要新开线程。要实现跨进程通信,需要借助AIDL(Android Interface Definition Language)。Android中的跨进程服务其实是采用C/S的架构,因而AIDL的目的就是实现通信接口。
.
Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且 Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。
AIDL 文件支持的数据类型
- 基本数据类型;
- String 和 CharSequence
- ArrayList ,里面的元素必须能够被 AIDL 支持;
- HashMap ,里面的元素必须能够被 AIDL 支持;
- Parcelable ,实现 Parcelable 接口的对象; 注意:如果 AIDL 文件中用到了自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。
- AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。
服务端
服务端创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可。
客户端
绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。
服务端访问权限管理
- 使用 Permission 验证,在 manifest 中声明
<permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE" android:protectionLevel="normal"/> <uses-permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE"/>
服务端 onBinder 方法中
public IBinder onBind(Intent intent) { //Permission 权限验证 int check = checkCallingOrSelfPermission("com.jc.ipc.ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; }
- Pid Uid 验证
详细代码:
// Book.aidl package com.jc.ipc.aidl; parcelable Book;
// IBookManager.aidl package com.jc.ipc.aidl; import com.jc.ipc.aidl.Book; import com.jc.ipc.aidl.INewBookArrivedListener; // AIDL 接口中只支持方法,不支持静态常量,区别于传统的接口 interface IBookManager { List<Book> getBookList(); // AIDL 中除了基本数据类型,其他数据类型必须标上方向,in,out 或者 inout // in 表示输入型参数 // out 表示输出型参数 // inout 表示输入输出型参数 void addBook(in Book book); void registerListener(INewBookArrivedListener listener); void unregisterListener(INewBookArrivedListener listener); }
// INewBookArrivedListener.aidl package com.jc.ipc.aidl; import com.jc.ipc.aidl.Book; // 提醒客户端新书到来 interface INewBookArrivedListener { void onNewBookArrived(in Book newBook); }
public class BookManagerActivity extends AppCompatActivity { private static final String TAG = BookManagerActivity.class.getSimpleName(); private static final int MSG_NEW_BOOK_ARRIVED = 0x10; private Button getBookListBtn,addBookBtn; private TextView displayTextView; private IBookManager bookManager; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_NEW_BOOK_ARRIVED: Log.d(TAG, "handleMessage: new book arrived " + msg.obj); Toast.makeText(BookManagerActivity.this, "new book arrived " + msg.obj, Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } }; private ServiceConnection mServiceConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bookManager = IBookManager.Stub.asInterface(service); try { bookManager.registerListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; private INewBookArrivedListener listener = new INewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, newBook).sendToTarget(); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.book_manager); displayTextView = (TextView) findViewById(R.id.displayTextView); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mServiceConn, BIND_AUTO_CREATE); } public void getBookList(View view) { try { List<Book> list = bookManager.getBookList(); Log.d(TAG, "getBookList: " + list.toString()); displayTextView.setText(list.toString()); } catch (RemoteException e) { e.printStackTrace(); } } public void addBook(View view) { try { bookManager.addBook(new Book(3, "天龙八部")); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onDestroy() { if (bookManager != null && bookManager.asBinder().isBinderAlive()) { Log.d(TAG, "unregister listener " + listener); try { bookManager.unregisterListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mServiceConn); super.onDestroy(); } }
public class BookManagerService extends Service { private static final String TAG = BookManagerService.class.getSimpleName(); // CopyOnWriteArrayList 支持并发读写,实现自动线程同步,他不是继承自 ArrayList private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); //对象是不能跨进程传输的,对象的跨进程传输本质都是反序列化的过程,Binder 会把客户端传递过来的对象重新转化生成一个新的对象 //RemoteCallbackList 是系统专门提供的用于删除系统跨进程 listener 的接口,利用底层的 Binder 对象是同一个 //RemoteCallbackList 会在客户端进程终止后,自动溢出客户端注册的 listener ,内部自动实现了线程同步功能。 private RemoteCallbackList<INewBookArrivedListener> mListeners = new RemoteCallbackList<>(); private AtomicBoolean isServiceDestroied = new AtomicBoolean(false); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { Log.d(TAG, "addBook: " + book.toString()); mBookList.add(book); } @Override public void registerListener(INewBookArrivedListener listener) throws RemoteException { mListeners.register(listener); } @Override public void unregisterListener(INewBookArrivedListener listener) throws RemoteException { mListeners.unregister(listener); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "老人与海")); mBookList.add(new Book(2, "哈姆雷特")); new Thread(new ServiceWorker()).start(); } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); int count = mListeners.beginBroadcast(); for (int i = 0; i < count; i++) { INewBookArrivedListener listener = mListeners.getBroadcastItem(i); if (listener != null) { listener.onNewBookArrived(book); } } mListeners.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public void run() { while (!isServiceDestroied.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() +1; Book newBook = new Book(bookId, "new book # " + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } @Nullable @Override public IBinder onBind(Intent intent) { //Permission 权限验证 int check = checkCallingOrSelfPermission("com.jc.ipc.ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } @Override public void onDestroy() { isServiceDestroied.set(true); super.onDestroy(); } }