1. 使用 Bundle
- 四大组件的三大组件:Activity、Service、Receiver 都支持在 Intent 中传递 Bundle 数据,因为 Bundle 实现了 Parcelable 接口,所以它可以方便地在不同的进程间传输。典型场景是:在一个进程中启动另一个进程的 Activity、Service、Receiver,把 Bundle 附加到 Intent 中发送出去
- 传输的数据必须能够被序列化,比如基本数据类型、实现了 Parcelable 接口的对象、实现了 Serializable 接口的对象以及一些 Android 支持的特殊对象。具体内容可以看 Bundle 这个类,查看 Bundle 所有支持的类型
2. 使用文件共享
两个进程通过读写同一个文件来交换数据,比如 A 进程把数据写入文件,B 进程通过读取这个文件来获取数据
- Windows 上,一个文件如果加了排斥锁将会导致其他线程无法对其进行访问,包括读和写
- Android 基于 Linux,使得其并发读写文件可以没有限制地进行,甚至两个线程同时对同一个文件进行写操作都是允许的,尽管这可能出问题
通过文件交换数据除了可以交换一些文本信息外,还可以序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象
- 通过文件共享这种方式共享数据对文件格式没有具体要求,可以是文本文件也可以是 XML 文件,只要读写双方约定好数据格式即可
- 通过文件共享的方式也是有局限性的,比如并发读写的问题,有可能读出的内容不是最新的,如果是并发写的话情况更严重
文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要求妥善处理并发读写的问题
SharedPreferences 是个特例
- SharedPreferences 是 Android 中提供的轻量级存储方案,通过键值对的方式来存储数据,底层实现上采用 XML 文件存储键值对
- 从本质上来说,SharedPreferences 也属于文件的一种,系统对它的读写有一定的缓存策略:在内存中会有一份 SharedPreferences 文件的缓存
- 因此在多进程模式下,系统对它的读写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很大几率会丢失数据
- 不建议在进程间通信中使用 SharedPreferences
Demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48// 在 MainActivity 中的修改
private void persistToFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1, "hello world", false);
File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
Log.d(TAG, "persist user: " + user);
} catch (IOException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectOutputStream);
}
}
}).start();
}
// 在 SecondActivity 中的修改
private void recoverFromFile() {
new Thread(
@Override
public void run() {
User user = null;
File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
if (cachedFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
user = (User) objectInputStream.readObject();
// 反序列化得到的对象只是在内容上和序列化之前的对象是一样的,但它们本质上还是两个对象
Log.d(TAG, "recover user: " + user);
} catch (IOException e | ClassNotFoundException e) {
e.printStackTrace();
} finally {
MyUtils.close(objectInputStream);
}
}
}
).start();
}
3. 使用 Messenger
Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL
Messenger 的两个构造方法
1
2
3
4
5
6
7public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}Messenger 一次处理一个请求,因此在服务端不用考虑线程同步的问题,因为服务端中不存在并发执行的情况
Messenger 的工作原理示意图:
Demo:
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG, "receive msg from Client: " + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你");
replyMessage.setData(bundle);
try {
client.send(replyMessage); // 当收到消息后,会立即回复一条消息给客户端
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
public IBinder onBind() {
return mMessenger.getBinder();
}
}
// 注册 Service,让其运行在单独的进程中
<service
android:name="com.szy.chapter_2.messenger.MessengerService"
android:process=":remote" >客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56public class MessengerActivity extends Activity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "hello, this is client");
msg.setData(data);
// 当客户端发送消息的时候,需要把接收服务端回复的 Messenger 通过 Message 的 replyTo 参数传递给服务端
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnectied(ComponentName className) {
}
};
@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();
}
// 为了接收服务端的回复,客户端也需要准备一个接收消息的 Messenger 和 Handler
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.i(TAG, "receive msg from Service: " + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
}
4. 使用 AIDL
Messenger 的局限:
- Messenger 是以串行的方式处理客户端发送的消息,不适合处理大量的并发请求
- Messenger 的作用主要是为了传递消息,无法做到跨进程调用服务端的方法
AIDL 文件支持的数据类型
- 基本数据类型:int、long、char、boolean、double 等
- String 和 CharSequence
- List: 只支持 ArrayList,里面每个元素都必须能够被 AIDL 支持
- Map: 只支持 HashMap,里面每个元素都必须被 AIDL 支持,包括 key 和 value
- Parcelable: 所有实现了 Parcelable 接口的对象
- AIDL: 所有的 AIDL 接口本身也可以在 AIDL 文件中使用
AIDL 规范
- 在 AIDL 文件中,自定义的 Parcelable 对象和 AIDL 对象必须要显示 import 进来,不管它们是否和当前的 AIDL 文件位于同一个包内
- 如果 AIDL 文件中用到了自定义的 Parcelable 对象,那么必须新建一个和它同名的 AIDL 文件,并在其中声明它为 Parcelable 类型
- AIDL 中除了基本数据类型,其他类型的参数必须标上方向:in、out 或者 inout,in 表示输入型参数、out 表示输出型参数、inout 表示输入输出型参数。要根据实际需要去指定参数类型,不能一概使用 out 或者 inout,因为这在底层实现是有开销的
- AIDL 接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口
- 为了方便 AIDL 的开发,建议把所有和 AIDL 相关的类和文件全部放入同一个包中
- AIDL 的包结构在服务端和客户端要保持一致,否则会运行出错,这是因为客户端需要反序列化服务端中和 AIDL 接口相关的所有类,如果类的完整路径不一致的话,就无法成功反序列化
Demo
AIDL 接口的创建
1
2
3
4
5
6
7
8
9
10
11
12
13// IBookManager.aidl
package com.szy.chapter_2.aidl;
import com.szy.chapter_2.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
// 适配观察者模式
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}1
2
3
4
5
6
7// 观察者模式
package com.szy.chapter_2.aidl;
import com.szy.chapter_2.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}远程服务端 Service 的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100public class BookManagerService extends Service {
private static final String TAG = "BMS";
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
// AIDL 方法是在服务端的 Binder 线程池中执行的,当多个客户端同时连接时会存在多个线程同时访问的情况
// 所以要在 AIDL 方法中处理线程同步,CopyOnWriteArrayList 支持并发读写,这里直接使用 CopyOnWriteArrayList 来进行自动的线程同步
// AIDL 中能够使用的 List 只有 ArrayList,但这里使用了 CopyOnWriteArrayList,而 CopyOnWriteArrayList 继承了 List 并没有继承 ArrayList
// CopyOnWriteArrayList 能正常工作的原因是 AIDL 中所支持的是抽象的 List,而 List 只是一个接口,因此虽然服务端返回的是 CopyOnWriteArrayList,但在 Binder 中会按照 List 的规范去访问数据并最终形成一个新的 ArrayList 传递回客户端,类似的还有 ConcurrentHashMap
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book> ();
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<IOnNewBookArrivedListener> ();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throww 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);
} else {
Log.d(TAG, "already exists.");
}
Log.d(TAG, "registerListener, size: " + mListenerList.size());
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if (mListenerList.contains(listener)) {
mListenerList.remove(listener);
Log.d(TAG, "unregister listener succeed.");
} else {
// Binder 会把客户端传递过来的对象重新转化并生成一个新的对象
// 对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程
Log.d(TAG, "not found, can not unregister.");
}
Log.d(TAG, "unregisterListener, current size: " + mListenerList.size());
}
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android));
mBookList.add(new Book(2, "iOS));
// 开启一个线程,每隔 5 秒就向书库中增加一本新书并通知所有感兴趣的用户
new Thread(new ServiceWorker()).start();
}
@Override
public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
Log.d(TAG, "onNewBookArrived, notify listeners: " + mListenerList.size());
for (int i = 0; i < mListenerList.size(); i++) {
IOnNewBookArrivedListener listener = mListenerList.get(i);
Log.d(TAG, "onNewBookArrived, notify listener: " + listener);
listener.onNewBookArrived(book);
}
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
// do background processing here...
while (!mIsServiceDestroyed.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();
}
}
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}1
2
3
4// 注册 Service
<service
android:name=".aidl.BookManagerService"
android:process=":remote" >客户端的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.d(TAG, "receive new book: " + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;
List<Book> list = bookManager.getBookList();
// 从 log 中可以发现,虽然在服务端返回的是 CopyOnWriteArrayList 类型,但是客户端收到的仍然是 ArrayList 类型
Log.i(TAG, "query book list, list type: " + list.getClass().getCanonicalName());
Log.i(TAG, "query book list: " + list.toString());
// 调用接口 addBook()
Book newBook = new Book(3, "Android Art");
Log.i(TAG, "add Book: " + newBook);
List<Book> newList = bookManager.getBookList();
Log.i(TAG, "query book list: " + newList.toString());
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
mRemoteBookManager = null;
Log.e(TAG, "binder died);
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook.sendToTarget());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
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 {
Log.i(TAG, "unregister listener: " + mOnNewBookArrivedListener);
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
}解注册失败的解决方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47// CopyOnWriteArrayList 的解注册方式在日常开发过程中时常使用,但是放到多进程中却无法奏效,因为 Binder 会把客户端传递过来的对象重新转化并生成一个新的对象
// 所以,虽然在注册和解注册的过程中使用的是同一个客户端对象,但是通过 Binder 传递到服务端后,会产生两个全新的对象
// 对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这也就是为什么 AIDL 中自定义的对象都必须要实现 Parcelable 接口的原因
// 使用 RemoteCallbackList 可以实现解注册功能
// RemoteCallbackList 是系统专门提供的用于删除跨进程 listener 的接口
// RemoteCallbackList 是一个泛型,支持管理任意的 AIDL 接口(所有的 AIDL 接口都继承自 IInterface 接口),其声明如下:public class RemoteCallbackList<E extends IInterface>
// RemoteCallbackList 的工作原理:内部有一个 Map 结构专门用来保存所有的 AIDL 回调,这个 Map 的 key 是 IBinder 类型,value 是 Callback 类型:ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback> ();
// Callback 中封装了真正的远程 listener,当客户端注册 listener 的时候,它会把这个 listener 的信息存入 mCallbacks 中,其中 key 和 value 分别通过下面的方式获得:IBinder key = listener.asBinder(); Callback value = new Callback(listener, cookie);
// 虽然多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但这些新生成的对象有一个共同点,那就是它们底层的 Binder 对象是同一个
// 利用这个特性,就可以实现解注册的功能:当客户端解注册的时候,只要遍历服务端所有的 listener,找出那个和解注册 listener 具有相同 Binder 对象的服务端 listener 并把它删掉即可,这就是 RemoteCallback 做的事
// RemoteCallback 还有一个很有用的功能,那就是当客户端进程终止后,它能够自动移除客户端所注册的 listener
// 另外,RemoteCallback 内部自动实现了线程同步的功能,所以在使用 RemoteCallback 来注册和解注册时,不需要做额外的线程同步工作
// RemoteCallbackList 的使用:创建一个 RemoteCallback 对象代替之前的 CopyOnWriteArrayList,修改 registerListener 和 unregisterListener 的实现,接着修改 onNewBookArrived 方法
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener> ();
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
// 需要注意的是,我们无法像操作 List 一样去操作它,尽管它的名字中也带个 List,但它并不是一个 List
// 遍历 RemoteCallbackList,必须要配对使用 beginBroadcast() 和 finishBroadcast(),哪怕仅仅是想获取 RemoteCallbackList 中的元素个数
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i ++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
try {
listener.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}Binder 线程池相关的 ANR
- 客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端线程会被挂起,此时如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞在这里,如果这个线程是 UI 线程的话,就会导致客户端 ANR
- 由于服务端的方法本身就运行在服务端的 Binder 线程池中,所以服务端方法本身就可以执行大量耗时操作,此时切记不要在服务端方法中开线程去执行异步任务,除非明确知道这么做的目的,否则不建议
- 同理,当远程服务端需要调用客户端的 listener 中的方法时,被调用的方法也运行在 Binder 线程池中,只不过是客户端的线程池,同样不能在服务端中调用客户端的耗时方法
Binder 健壮性
Binder 是可能意外死亡的,往往是因为服务端进程意外停止,此时需要重连服务,有两种方法
- 第一种方法是给 Binder 设置 DeathRecipient 监听,当 Binder 死亡时,会收到
binderDied()
方法的回调,在回调方法中可以重连远程服务 - 第二种方法是在
onServiceDisconnected()
中重连远程服务
- 第一种方法是给 Binder 设置 DeathRecipient 监听,当 Binder 死亡时,会收到
两种方法的区别是:
onServiceDisconnected()
在客户端的 UI 线程中被回调,而binderDied()
在客户端的 Binder 线程池中被回调。所以,在binderDied()
回调方法中不能访问 UI
在 AIDL 中使用权限验证功能
默认情况下,上述远程服务任何人都可以连接,显然是有安全隐患的。在 AIDL 中进行权限验证,有两种常用的方法
第一种方法:可以在
onBind()
中进行验证,验证不通过就直接返回 null,验证方式有多种,比如使用 permission 验证(这种方法同样适用于 Messenger 中)1
2
3<permission
android:name="com.szy.chapter_2.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />1
2
3
4
5
6
7public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.szy.chapter_2.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}1
2
3// 如果我们自己内部的应用想绑定到我们的服务中,只需要在它的 AndroidMenifest 文件中采用如下方式使用 permission 即可
<uses-permission
android:name="com.szy.chapter_2.permission.ACCESS_BOOK_SERVICE" />第二种方法:可以在服务端的
onTransact()
方法中进行权限验证,如果验证失败就返回 false,验证方式有很多,可以采用 permission 验证。还可以采用 Uid 和 Pid 来做验证,通过getCallingUid()
和getCallingPid()
可以拿到客户端所属应用的 Uid 和 Pid,通过这两个参数可以做一些验证工作,比如验证包名1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int check = checkCallingOrSelfPermission("com.szy.chapter_2.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = package[0];
}
if (!packageName.startWith("com.szy")) {
return false;
}
return super.onTransact(code, data, reply, flags);
// 还有其他方法可以做权限验证,比如为 Service 指定 android:permission 属性等
}
5. 使用 ContentProvider
ContentProvider 是 Android 提供的专门用于不同应用间进行数据共享的方式
和 Messenger 一样,ContentProvider 的底层实现同样是 Binder
系统预置了很多 ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过 ContentResolver 的
query()
、update()
、insert()
和delete()
方法即可创建一个自定义的 ContentProvider 只需要继承 ContentProvider 类并实现六个抽象方法即可:
onCreate()
、query()
、update()
、insert()
、delete()
和getType()
onCreate()
: 代表 ContentProvider 的创建,一般来说需要做一些初始化工作getType()
: 用来返回一个 Uri 请求所对应的 MIME 媒体类型,比如图片、视频等,如果应用不关注这个选项,可以直接在这个方法中返回null
或者*/*
- 剩下的四个方法对应于 CRUD 操作,实现对数据表的增删改查功能
- 根据 Binder 的工作原理,这六个方法均运行在 ContentProvider 的进程中,除了
onCreate()
由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在 Binder 线程池中
ContentProvider 主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,它们都具有行和列的层次性,行往往对应一条记录,而列对应一条记录中的一个字段
ContentProvider 还支持文件数据,比如图片、视频等
- 文件数据和表格数据的结构不同,处理文件数据时,可以在 ContentProvider 中返回文件的句柄给外界从而让文件来访问 ContentProvider 中的文件信息
- Android 系统提供的 MediaStore 功能就是文件类型的 ContentProvider
虽然 ContentProvider 的底层数据看起来很像一个 SQLite 数据库,但是 ContentProvider 对底层数据的存储方式没有任何要求,既可以使用 SQLite 数据库,也可以使用普通的文件,甚至可以采用内存中的一个对象来进行数据的存储
Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124// 创建一个 BookProvider 类,继承 ContentProvider 并实现了六个必须需要实现的抽象方法
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
// 通过 BookProvider 向外界提供数据库 DbOpenHelper 中的图书信息和用户信息
public static final String AUTHORITY = "com.szy.chapter_2.book.provider";
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 {
// 使用 UriMatcher 的 addURI() 方法将 Uri 和 Uri_Code 关联到一起
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;
}
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate(), current thread: " + Thread.currentThread().getName());
mContext = getContext();
// ContentProvider 创建时,初始化数据库(这里仅仅是为了演示,实际使用中不推荐在主线程中进行耗时的数据库操作)
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 book values(5, 'Html5')");
mDb.execSQL("insert into user values(1, 'jack', 1);");
mDb.execSQL("insert into user values(2, 'david', 0);");
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(TAG, "query(), current thread: " + Thread.currentThread().getName());
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Override
public String getType(Uri uri) {
Log.d(TAG, "getType());
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.d(TAG, "insert()");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mDb.insert(table, null, values);
// update()、insert()、delete() 方法会引起数据源的改变,此时需要通过 ContentProvider 的 notifyChange() 方法来通知外界当前 ContentProvider 中的数据已经发生改变
// 要观察一个 ContentProvider 中的数据改变情况,可以通过 ContentResolver 的 registerContentObserver() 方法来注册观察者,通过 unregisterContentObserver() 方法来解除观察者
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.d(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(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d(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;
}
// 需要注意的是,query()、update()、insert()、delete() 四大方法是存在多线程并发访问的,因此方法内部要做好线程同步
// 本例中,由于采用的是 SQLite 并且只有一个 SQLiteDatabase 的连接,所以可以正确应对多线程的情况
// 具体原因是 SQLiteDatabase 内部对数据库的操作是有同步处理的,但是如果通过多个 SQLiteDatabase 对象来操作数据库就无法保证线程同步,因为 SQLiteDatabase 对象之间无法进行线同步
// 如果 ContentProvider 的底层数据集是一块内存的话,比如是 List,在这种情况下对 List 的遍历、插入、删除操作就需要进行线程同步,否则会引起并发错误
}1
2
3
4
5
6
7
8
9// 注册 BookProvider
<provider
android:name=".provider.BookProvider"
android:authorities="com.szy.chapter_2.book.provider" // android:authorities 是 ContentProvider 的唯一标识,必须是唯一的
android:permission="com.szy.PROVIDER" // 添加了权限,外界应用如果想访问 BookProvider,就必须声明该权限
android:process=":provider" >
// ContentProvider 的权限还可以细分为读权限和写权限,分别对应 android:readPermission 和 android:writePermission
// 如果分别声明了读权限和写权限,那么外界应用也必须依次声明相应的权限才可以进行读写操作,否则外界应用会异常终止1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42// 在外部应用中访问 ContentProvider
public class ProviderActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
// Uri uri = Uri.parse("content://com.szy.chapter_2.book.provider");
// 从 log 中可以看出,BookProvider 中的 query() 方法被调用了三次,并且这三次调用不在同一个线程中,它们运行在一个 Binder 线程中
// 同样地,update()、insert()、delete() 也运行在 Binder 线程中。另外,onCreate() 运行在主线程中,所以不能做耗时操作
// getContentResolver().query(uri, null, null, null, null);
// getContentResolver().query(uri, null, null, null, null);
// getContentResolver().query(uri, null, null, null, null);
Uri bookUri = Uri.parse("content://com.szy.chapter_2.book.provider/book");
ContentValues values = new ContentValues();
values.put("_id", 6);
values.put("name", "编程之美");
getContentResolver().insert(bookUri, values);
Cursor bookCursor = getContentResolver().query(bookUri, new String[] {"_id", "name"}, null, null, null);
while (bookCursor.moveToNext()) {
Book book = new Book();
book.bookId = bookCursor.getInt(0);
book.bookName = bookCursor.getString(1);
Log.d(TAG, "query book: " + book.toString());
}
bookCursor.close();
Uri userUri = Uri.parse("content://com.szy.chapter_2.book.provider/user");
Cursor userCursor = getContentResolver().query(userUri, new String[] {"_id", "name", "sex"}, null, null, null);
while (userCursor.moveToNext()) {
User user = new User();
user.userId = userCursor.getInt(0);
user.userName = userCursor.getString(1);
user.isMale = user.Cursor.getInt(2) == 1;
Log.d(TAG, "query user: " + user.toString());
}
userCursor.close();
}
// ContentProvider 除了支持对数据源的增删改查这四个操作,还支持自定义调用,这个过程是通过 ContentResolver 的 Call() 方法和 ContentProvider 的 Call() 来完成的
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 需求是外部应用可以通过 ContentProvider 来访问图书信息和用户信息,需要一个数据库来管理图书信息和用户信息
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private static final int DB_VERSION = 1;
// 图书和用户信息表
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT," + "sex INT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO ignored
}
}
6. 使用 Socket
Socket 也称套接字,是网络通信中的概念,分为流式套接字和用户数据报套接字两种,分别对应于网络传输控制层中的 TCP 和 UDP 协议
Socket 本身可以支持传输任意字节流
使用 Socket 来进行跨进程通信,有两点需要注意
首先需要声明权限
1
2<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />其次要注意不能在主线程中访问网络,一是因为网络操作大多都是耗时的,而是因为在 Android 4.0 及以上的设备中运行时,会抛出异常:
android.os.NetorkOnMainThreadException
通过 Socket 不仅仅能实现进程间通信,还可以实现设备间通信,前提是这些设备之间的 IP 地址互相可见
Demo
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82public class TCPServerService extends Service {
private boolean mIsServiceDestroyed = false;
private String[] mDefinedMessages = new String[] {"东海有岛夷,北山尽愁怨", "荡涤谁氏子,安得辞浮贱","子期竟早亡,牙琴从此绝", "琴绝最伤情,朱华春不荣", "后来有千日,谁与共平生"};
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
public IBinder onBind(Intent intent) {
return null;
}
public void onDestroy() {
mIsServiceDestroyed = true;
super.onDestroy();
}
private class TcpServer implements Runnable {
"resource") (
public void run() {
ServerSocket serverSocket = null;
try {
// 监听本地 8688 端口
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.err.println("establish tcp server failed, port: 8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestroyed) {
try {
// 接收客户端请求
final Socket client = serverSocket.accept();
System.out.println("accept");
new Thread() {
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
// 用于接收客户端消息
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 用于向客户端发送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
out.println("欢迎你");
while (!mIsServiceDestroyed) {
String srt = in.readLine();
System.out.println("msg from client: " + str);
if (str == null) {
// 客户端断开连接
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg);
System.out.println("send: " + msg);
}
System.out.println("client quit.");
// 关闭流
MyUtils.close(out);
MyUtils.close(in);
client.close();
}
}客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116public class TCPClientActivity extends Activity implements OnClickListener {
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
private static final int MESSAGE_SOCKET_CONNECTED = 2;
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG:
mMessageTextView.setText(mMessageTextView.getText() + (String) msg.obj);
break;
case MESSAGE_SOCKET_CONNECTED:
mSendButton.setEnabled(true);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
mMessageTextView = (TextView) fiindViewById(R.id.msg_container);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close(); // 当 Activity 退出时,关闭当前的 Socket
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self" + time + ":" + msg + "\n";
mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
}
}
}
@SuppressLint("SimpleDateFormat")
private String formatDateTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)".format(new Date(time));
}
private void connectTCPServer () {
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000); // 休眠机制,每次超时重连重试的时间间隔为 1000 毫秒
System.out.println("connect tcp server failed, retry...");
}
}
try {
// 接收服务端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msg = br.readLine();
System.out.println("receive: " + msg);
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg + "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg).sendToTarget();
}
}
// 当 Activity 退出时,退出循环并终止线程
System.out.println("quit...");
MyUtils.close(mPrintWriter);
MyUtils.close(br);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}