Android中的IPC方式(二)
在Android中,IPC的方式有多种多样,有很多的方式其实我们已经用到了,只是很多时候我们自己并没发觉这是IPC方式的一种。比如:通过在Intent中附加extras来传递信息,或者通过共享文件的方式来共享数据,还可以采用我们之前讲过的binder方式来跨进程通信等等,那么下面我们就来看一下Android中的IPC方式
Android中的IPC方式
使用Bundle
在android的四大组件中,Activity,Service,Receiver都是支持在Intent中传递Bundle数据的,查看源码可知:
Bundle实现了Parcelable接口,所以它可以很方便地在不同的进程中传输。正是基于这一点,当我们在一个进程中启动了另一个进程的Activity,Service和Receiver,我们就可以很方便的携带数据。当然,被携带的数据必须是可以被序列化的数据。在所有的IPC方式中,这是最简单的通信方式之一。
使用文件共享
文件共享也是一种不错的进程间通信方式之一,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程从文件中获取数据。在Windows上,一个文件如果被加了排斥锁将会导致其他线程无法对其进行访问,包括读和写,而由于Android系统基于Linux,使得其并发读/写文件可以没有限制地进行,甚至两个线程同时对同一个文件进行读写操作都是可以的,尽管这可能会出现问题。通过文件交换数据很好使用,除了可以交换一些文本信息外,我们还可以序列化一个对象到文件系统中同时从另一个进程中恢复这个对象,下面我们来测试一下这种方式:
首先声明一个Object对象并且进行序列化
public class User implements Serializable { private String name; private int age; private boolean isMale; public User(String name, int age, boolean isMale) { this.name = name; this.age = age; this.isMale = isMale; } @Override public String toString() { return name + "-->" + age + "-->" + isMale; }}
将数据保存到文件中
// 序列化一个User对象到SD卡上的一个文件里 new Thread(new Runnable() { @Override public void run() { User user = new User("steven", 22, false); String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "user"; File file = new File(filePath); if (!file.exists()) { file.mkdirs(); } String userPath = filePath + File.separator + "user.txt"; File file1 = new File(userPath); ObjectOutputStream objectOutputStream = null; try { objectOutputStream = new ObjectOutputStream(new FileOutputStream(file1)); objectOutputStream.writeObject(user); Log.e("TAG", "persist user:" + user.toString()); } catch (IOException e) { e.printStackTrace(); } finally { try { objectOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }).start();
从文件中取出数据
new Thread(new Runnable() { @Override public void run() { User user = null; String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "user" + File.separator + "user.txt"; File file = new File(path); if (file.exists()) { ObjectInputStream inputStream = null; try { inputStream = new ObjectInputStream(new FileInputStream(file)); user = (User) inputStream.readObject(); Log.e("TAG", "recover user:" + user.toString()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }).start();
这样就完成了进程间通信的功能,注意,由于对文件的操作属于IO的操作,所以我们必须在子线程中进行。对比而言,这种方式也是比较简单的,但是这种方式是有局限性的,即我们不能进行并发读/写,因为这可能会导致拿到的数据不是最新的数据,如果是并发写的话那就更严重了。
这里我们还需要注意一种情况,那就是对SharedPreferences的处理,我们知道SharedPreferences是Android提供的一种轻量级的存储方式,它通过键值对的形式来保存数据,在底层它采用的是xml文件来保存,因此它也属于文件形式的一种,但是由于系统对它的读/写操作有一定的缓存策略,导致在多进程的情况下它的读写操作变的非常的不牢靠,所以在多进程的情况下不建议使用SharedPreferences。
使用Messenger
Messenger咋一眼看过去和Message好像,其实Messenger翻译过来的意思是信使,例如古代的飞鸽传书的鸽子,那传书的书在程序中是由谁代替的呢?想必大家都已经猜到了,没错就是由我们的Message来代替的。我们在Message中放入我们需要传递的数据,通过Messenger就可以轻松地实现数据的进程间通信了。Messenger是一种轻量级的IPC方式,它的底层实现是AIDL,查看Messenger的源码可知:
public final class Messenger implements Parcelable { private final IMessenger mTarget; public Messenger(Handler target) { mTarget = target.getIMessenger(); } public IBinder getBinder() { return mTarget.asBinder(); } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }}
Messenger的源码不多,只有100多行,我挑选了一些关键的代码,看完上面的这些你就会明白Messenger的底层还是用的AIDL,由于Messenger对AIDL做了封装,因此Messenger使用起来还是很方便的。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为在服务端中不存在并发执行的情况。实现一个Messenger有如下几个步骤(分为服务端和客户端):
服务端:首先我们需要创建一个Service来处理客户端的请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBinder中返回这个Messenger对象底层的Binder即可
public class MessengerService extends Service { private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.e("TAG", "receive msg from client:" + msg.getData().getString("msg")); break; default: super.handleMessage(msg); } } } private final Messenger messenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); }}
当然仅仅这样还不够,因为现在还是处于同一个进程中,所以我们还需要将Service放入另外一个进程中
好,接下来我们来看看在客户端我们如何操作
客户端:客户端的实现也比较简单,首先需要绑定远程进程的Service,绑定成功后,根据服务端返回的binder对象创建Messenger对象并使用此对象向服务端发送消息。
public class MessengerActivity extends AppCompatActivity { private Messenger mService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = new Messenger(service); Message message = Message.obtain(null, 1); Bundle bundle = new Bundle(); bundle.putString("msg", "hello,this is client"); message.setData(bundle); try { mService.send(message); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Intent intent = new Intent(this, MessengerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); }}
最后看下打印的结果:
12-11 14:59:24.948 27492-27492/com.stevences.androidipc:remote E/TAG: receive msg from client:hello,this is client
看来我们使用Messenger来传递数据已经成功了,注意,这里是从客户端向服务端发送数据,假如我们需要从服务端向客户端发送数据我们应该怎么处理呢?还是以上面的例子为例,我们在上面的基础上进行修改
首先我们修改我们的客户端:既然有发送也有接收,那么我们需要在发送的时候就指定接收的Messenger,即:
public class MessengerActivity extends AppCompatActivity { private Messenger mService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = new Messenger(service); Message message = Message.obtain(null, 1); Bundle bundle = new Bundle(); bundle.putString("msg", "hello,this is client"); message.setData(bundle); // 指定接收的Messenger message.replyTo = messengerGet; try { mService.send(message); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Intent intent = new Intent(this, MessengerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } private Messenger messengerGet = new Messenger(new MessengerHandler()); private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { Log.e("TAG", "receive msg from service:"); switch (msg.what) { case 2: Log.e("TAG", "receive msg from service:" + msg.getData().getString("msg")); break; default: super.handleMessage(msg); } } }}
接着在我们的服务端我们需要在接收的同时发送消息出去
public class MessengerService extends Service { private static class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.e("TAG", "receive msg from client:" + msg.getData().getString("msg")); // 收到信息后进行回复 Messenger messenger = msg.replyTo; Message message = Message.obtain(null, 2); Bundle bundle = new Bundle(); bundle.putString("msg", "好的,您的信息已经收到,我们马上处理"); message.setData(bundle); try { messenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private final Messenger messenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); }}
最后的结果为
receive msg from client:hello,this is client receive msg from service:好的,您的信息已经收到,我们马上处理
这样就实现了客户端和服务端的通信,这里再贴一张图来更好的理解Messenger
在这里扩展一下:
我们知道在Android中Service的启动方式有两种,一种是startService(),一种就是bindService(),startService()不涉及交互的问题,而bindService()就是为了解决交互问题而设计的。而一般情况下我们使用bindService()是怎么去写的呢
public class MyService extends Service { public static final String TAG = "MyService"; private MyBinder mBinder = new MyBinder(); @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate() executed"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand() executed"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy() executed"); } @Override public IBinder onBind(Intent intent) { return mBinder; } class MyBinder extends Binder { public void startDownload() { Log.d("TAG", "startDownload() executed"); // 执行具体的下载任务 } } }
注意一下:我们新建了一个内部类MyBinder,然后继承自Binder,然后在onBind()里面返回MyBinder的实例,这样就可以实现交互。由此可知,Messenger时间上底层还是用的binder。对Service还不熟悉的同学可以看下这篇
https://blog.csdn.net/qq_27970997/article/details/70210313
使用AIDL
通过上面的介绍我们知道Messenger是以串行的方式来处理客户端发送的消息,如果有大量的消息同时发送到服务端,服务端仍然只能一个个去处理,这样太影响效率了。同时,Messenger的作用是为了传递数据,很多时候我们需要跨进程去调用方法,这个时候Messenger就不适用了,但是我们的AIDL可以实现跨进程的方式调用方法,接下来我们就一起来看看AIDL吧
服务端:首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可
public class BookManagerService extends Service { private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>(); private Binder mBinder = new IBookManager.Stub() { @Override public List getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "IOS")); } @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; }}
注意,Book类必须进行序列化,否则直接报错
public class Book implements Parcelable { private int bookId; private String bookName; public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } protected Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } public static final Creator CREATOR = new Creator() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(bookId); dest.writeString(bookName); } @Override public String toString() { return bookId + "--->" + bookName; }}
接下来是两个比较重要的AIDL文件
// Book.aidlpackage com.stevences.androidipc.aidl;parcelable Book;
// IBookManager.aidlpackage com.stevences.androidipc.aidl;import com.stevences.androidipc.aidl.Book;interface IBookManager { List getBookList(); void addBook(in Book book);}
这样服务端就完成了,在这里我们需要注意以下几点:
1》 在AIDL文件中,并不是所有的数据类型都是可以使用的,AIDL文件支持的类型有以下几种:
(1)基本数据类型
(2)String和CharSequence
(3)List:只支持ArrayList,里面每个元素都必须能够被AIDL支持
(4)Map:只支持HashMap,里面每个元素都必须能够被AIDL支持,包括key和value
(5)Parcelable:所有实现了Parcelable接口的对象
(6)AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
注意:自定义的Parcelable对象和AIDL对象必须要显示import进来
而客户端的实现就比较简单了,首先需要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后通过这个接口就可以去调用服务端的远程方法了
public class AIDLActivity extends AppCompatActivity { private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { List list = bookManager.getBookList(); Log.e("TAG", "query book list ,list type:" + list.getClass().getCanonicalName()); Log.e("TAG", "query book list :" + list.toString()); } catch (Exception e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_aidl); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); }}
最后打印的结果为:
query book list ,list type:java.util.ArrayListquery book list :[1--->Android, 2--->IOS]
接下来我们继续对这个功能扩展一下,我们想在有新图书的时候发送通知
首先新建一个接口用来监听,注意这里我们只能去创建AIDL的接口
package com.stevences.androidipc.aidl;import com.stevences.androidipc.aidl.Book;// Declare any non-default types here with import statementsinterface IOnNewBookArrivedListener { void onNewBookArrived(in Book book);}
接着我们在我们的IBookManager.aidl里面添加两个方法用来增加监听和取消监听
package com.stevences.androidipc.aidl;import com.stevences.androidipc.aidl.Book;import com.stevences.androidipc.aidl.IOnNewBookArrivedListener;interface IBookManager { List getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener);}
接着修改我们的服务端
public class BookManagerService extends Service { private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList mListenerList = new CopyOnWriteArrayList<>(); // 原子性操作,具有排他性,保证线程安全 private AtomicBoolean mInServiceDestroyed = new AtomicBoolean(false); private Binder mBinder = new IBookManager.Stub() { @Override public List getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { if (!mListenerList.contains(listener)) { mListenerList.add(listener); } } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { if (mListenerList.contains(listener)) { mListenerList.remove(listener); } } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "IOS")); new Thread(new ServiceWorker()).start(); } @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { mInServiceDestroyed.set(true); super.onDestroy(); } private class ServiceWorker implements Runnable { @Override public void run() { while (!mInServiceDestroyed.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(); } } } } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); for (int i = 0; i < mListenerList.size(); i++) { IOnNewBookArrivedListener listener = mListenerList.get(i); listener.onNewBookArrived(book); } }}
稍微讲解一下:
1》AtomicBoolean 的基本特性就是在多线程环境下,执行这些类实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入。
2》CopyOnWriteArrayList支持并发读/写,并且支持线程同步
3》在onCreate()方法中我们开启一个线程5秒钟去增加一本书,并且发送给所有的观察者
那么我们的客户端也要做相应的修改
public class AIDLActivity extends AppCompatActivity { private static final int MESSAGE_NEW_BOOK_ARRIVED = 1; private IBookManager mRemoteBookManager; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: Log.e("TAG", "receive new book :" + msg.obj); break; default: super.handleMessage(msg); } } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { mRemoteBookManager = bookManager; List list = bookManager.getBookList(); Log.e("TAG", "query book list :" + list.toString()); Book newBook = new Book(3, "android开发艺术探索"); bookManager.addBook(newBook); List newList = bookManager.getBookList(); Log.e("TAG", "query book list :" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (Exception e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mRemoteBookManager = null; } }; private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book book) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_aidl); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); }}
首先要注册IOnNewBookArrivedListener到远程服务端,这样当有新书的时候才能通知当前客户端,同时我们要在Activity退出时解除这个注册;另外一个方面,当有新书时,服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法,但是由于这个方法是在客户端的Binder线程池中执行的,因此,为了便于UI操作,我们需要创建一个Handler可以将其切换到客户端的主线程中去执行
到这里,AIDL的基本使用方法就已经介绍完了,这里再啰嗦几点:
1》客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而一旦这个客户端线程是UI线程的话,就会导致客户端ANR
2》为了避免出现(1)的情况,在实际的开发过程中我们要避免在UI线程中去访问远程方法。由于客户端的onServiceConnected和onServiceDisConnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法
3》由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程去执行异步任务
使用ContentProvider
ContentProvider是android的四大组件之一,也是Android中提供的专门用于不同应用之间进行数据共享的方式之一,从这点来看,它天生就适合进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder,由此可见Binder在Android中是多么的重要。接下来我们一起来看看到底如何使用ContentProvider。
相对于AIDL而言,ContentProvider使用起来要简单的多,因为Android本身就对它进行了封装,使得我们无须关心底层细节即可轻松实现IPC。
首先我们新建一个BookProvider来继承自ContentProvider
public class BookProvider extends ContentProvider { public static final String AUTHORITY = "com.stevences.androidipc.contentprovider"; public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book"); public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user"); public static final int BOOK_URI_CODE = 0; public static final int USER_URI_CODE = 1; private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE); sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE); } private Context mContext; private SQLiteDatabase mDb; private String getTableName(Uri uri) { String tableName = null; switch (sUriMatcher.match(uri)) { case BOOK_URI_CODE: tableName = DbOpenHelper.BOOK_TABLE_NAME; break; case USER_URI_CODE: tableName = DbOpenHelper.USER_TABLE_NAME; break; default: break; } return tableName; } // 创建ContentProvider @Override public boolean onCreate() { Log.e("TAG", "onCreate"); mContext = getContext(); initProviderData(); return true; } private void initProviderData() { mDb = new DbOpenHelper(mContext).getWritableDatabase(); mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME); mDb.execSQL("delete from " + DbOpenHelper.USER_TABLE_NAME); mDb.execSQL("insert into book values(3,'Android');"); mDb.execSQL("insert into book values(4,'Ios');"); mDb.execSQL("insert into user values(1,'jake',1);"); mDb.execSQL("insert into user values(2,'jasmine',0);"); } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { Log.e("TAG", "query,current thread:" + Thread.currentThread()); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI:" + uri); } return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null); } // 返回一个Uri请求所对应的MIME类型 @Nullable @Override public String getType(@NonNull Uri uri) { Log.e("TAG", "getType"); return null; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { Log.e("TAG", "insert"); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI:" + uri); } mDb.insert(table, null, values); mContext.getContentResolver().notifyChange(uri, null); return uri; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { Log.e("TAG", "delete"); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI:" + uri); } int count = mDb.delete(table, selection, selectionArgs); if (count > 0) { getContext().getContentResolver().notifyChange(uri, null); } return count; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { Log.e("TAG", "update"); String table = getTableName(uri); if (table == null) { throw new IllegalArgumentException("Unsupported URI:" + uri); } int row = mDb.update(table, values, selection, selectionArgs); if (row > 0) { getContext().getContentResolver().notifyChange(uri, null); } return row; }}
接着我们对这个BookProvider进行注册
注意:authorities是ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider,因此authorities必须是唯一的。
注册结束之后我们就可以在外部应用访问它了
Uri uri = Uri.parse("content://com.stevences.androidipc.contentprovider/book");getContentResolver().query(uri,null,null,null,null);
这样就可以执行我们的query方法了,需要注意的是,query、update、insert、delete四大方法是存在多线程并发访问的,因此方法内部要做好线程同步
使用Socket
关于Socket,这里我就不详解了,可以看我的另外一篇博客,那里面有你想要的内容