Android中的IPC方式
Bundle
典型应用场景
当我们在一个进程中启动了另一进程的Activity,Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。当然这个数据必须能够被序列化,比如基本数据类型,实现了Parcelable接口的对象,实现了Serializable接口的对象以及一些Android支持的特殊对象。
特殊使用场景
比如A进程正在进行一个计算,计算完成后它要启动B进程的一个组件并把计算结果传递给B进程,可是遗憾的是此结果不支持放入Bundle中。这个时候可以使用如下方法:
通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台进行计算,计算完毕后再启动B进程中的目标组件,由于Service也在B进程中,所以目标组件可以直接获得计算结果。
使用文件共享
概括:两个进程通过读写同一个文件来交换数据
过程:
将数据序列化到文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24private void persistToFile(){
new Thread(new Runnable() {
public void run() {
File baseDir=getBaseContext().getFilesDir();
User user=new User(false,1,"hello world");
File dir=new File(baseDir,MyConstants.CHAPTER_2_PATH);
if(!dir.exists()){
dir.mkdirs();
}
File cachedFile=new File(dir,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();
}反序列化读取
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
27private void recoverFromFile(){
new Thread(new Runnable() {
public void run() {
User user=null;
File baseDir=getBaseContext().getFilesDir();
File dir = new File(baseDir, MyConstants.CHAPTER_2_PATH);
File cacheFile=new File(dir,MyConstants.CACHE_FILE_PATH);
if(cacheFile.exists()){
ObjectInputStream objectInputStream=null;
try {
objectInputStream =new ObjectInputStream(
new FileInputStream(cacheFile)
);
user=(User) objectInputStream.readObject();
Log.d(TAG, "recover user:"+user);
}catch ( IOException e){
e.printStackTrace();
}catch (ClassNotFoundException e){
e.printStackTrace();
}finally {
MyUtils.close(objectInputStream);
}
}
}
}).start();
}局限性:受并发读/写的影响,故此方法适合对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写的问题
Messenger
服务端进程
首先需要创建一个Service来处理连接请求,同时创建一个Handle并通过它来创建一个Messener对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
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
33public class MessengerService extends Service {
private static final String TAG = "MessengerService" ;
public 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(Intent intent) {
return mMessenger.getBinder();
}
}让service运行在单独的进程中
客户端进程
首先要绑定服务端的service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端可以回应客户端,就和服务端一样,我们需要创建一个Handle并通过它来创建一个Messener对象,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端
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 AppCompatActivity {
private static final String TAG="MessengerActivity";
private Messenger mService;
private ServiceConnection mConnection=new ServiceConnection() {
public void onServiceConnected(ComponentName name, 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);
//注意下面这句话
msg.replyTo=mGetReplyMessenger;
try {
mService.send(msg);
}catch (RemoteException e){
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName name) {
}
};
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);
}
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
//为了实现回复客户端的功能新增代码
private Messenger mGetReplyMessenger=new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler{
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);
}
}
}
}工作原理示意图

.
AIDL
AIDL文件支持的数据类型
- 基本数据类型(int,long,char,boolean,double等)
- String和CharSequence
- List:只支持ArrayList,里面每个元素都能被AIDL支持
- Map:只支持HashMap,里面每个元素都能被AIDL支持,包括key和value
- Parcelable:所有实现了Parcelable接口的对象
- AIDL:所有AIDL接口本身也可以在AIDL文件中使用
注意事项
- 自定义的Parcelable对象和AIDL对象必须要显式的import进来
- 如果AIDL文件用到了自定义的Parcelable对象,那么必须新建一个与他同名的AIDL文件,并在其中声明他是Parcelable类型
- AIDL中除了基本数据类型,其他类型的参数都必须标上方向:in,out和inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数
- AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口,
实现当有新书时把信息告知给用户的功能
新建AIDL接口,让服务端能够主动通知客户端
1
2
3
4
5
6//导入自定义的数据类型。
import com.example.ipctest.Book;
interface IOnNewBookArrivedListener {
//in是从调用方指向被调用法,out相反
void onNewBookArrived(in Book newBook);
}在原来的AIDL接口中添加两个新方法
1
2
3
4
5
6
7
8
9
10
11//导入自定义的数据类型。
import com.example.ipctest.Book;
import com.example.ipctest.IOnNewBookArrivedListener;
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
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203public class BookManagerService extends Service {
private static final String TAG = "BMS";
//作为一个线程安全的标志位(flag),用来记录 Service 是否已经被销毁。可以在多线程中启用
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
private Binder mBinder = new IBookManager.Stub() {
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
// 直接调用 register 方法即可。
// 它内部会自动处理重复注册的情况,所以你不需要手动检查 contains。
mListenerList.register(listener);
Log.d(TAG, "registerListener complete.");
// beginBroadcast() 会返回当前有效的监听器数量
int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.d(TAG, "current listener size: " + N);
}
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
// unregister() 方法会返回一个 boolean 值,表示是否成功移除了监听器。
// 这个方法是原子操作,并且能正确处理 Binder 对象。
boolean success = mListenerList.unregister(listener);
if (success) {
Log.d(TAG, "unregister listener succeed.");
} else {
Log.d(TAG, "not found, can not unregister.");
}
// 获取当前有效监听器数量的正确方式
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.d(TAG, "unregisterListener, current size: " + N);
}
};
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
new Thread(new ServiceWorker()).start();
}
public IBinder onBind(Intent intent) {
return mBinder;
}
public void onDestroy() {
// 通知后台线程停止工作
mIsServiceDestoryed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) {
// 1. 先将书加入列表
mBookList.add(book);
Log.d(TAG, "New book added: " + book + ". Notifying listeners...");
// 2. 开始广播,获取有效监听器数量
final int N = mListenerList.beginBroadcast();
try {
// 3. 在 beginBroadcast 和 finishBroadcast 之间遍历并通知
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
try {
Log.d(TAG, "Notifying listener: " + listener);
// 调用客户端的回调方法
listener.onNewBookArrived(book);
} catch (RemoteException e) {
// 某个客户端可能在调用期间死亡或发生其他远程错误
// RemoteCallbackList 会在下次 beginBroadcast 时自动清理它
e.printStackTrace();
}
}
}
} finally {
// 4. 确保在任何情况下都结束广播(即使循环中发生异常)
mListenerList.finishBroadcast();
}
}
private class ServiceWorker implements Runnable {
public void run() {
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId=mBookList.size()+1;
Book newBook = new Book(bookId, "new book#" + bookId);
onNewBookArrived(newBook);
}
}
}
}public class BookManagerService extends Service {
private static final String TAG="BMS";
private AtomicBoolean mIsServiceDestoryed=new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList=new CopyOnWriteArrayList<Book>();
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList=new CopyOnWriteArrayList<IOnNewBookArrivedListener>();
private Binder mBinder=new IBookManager.Stub() {
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
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());
}
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if(mListenerList.contains(listener)){
mListenerList.remove(listener);
Log.d(TAG, "unregister listener succeed.");
}else {
Log.d(TAG, "not found,can not unregister.");
}
Log.d(TAG, "unregisterListener:,current size:"+mListenerList.size());
}
};
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(2,"Ios"));
new Thread(new ServiceWorker()).start();
}
public IBinder onBind(Intent intent) {
return mBinder;
}
public void onDestroy() {
mIsServiceDestoryed.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 listeners:"+listener);
listener.onNewBookArrived(book);
}
}
private class ServiceWorker implements Runnable{
public void run() {
while(!mIsServiceDestoryed.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();
}
}
}
}
}客户端
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
72public class BookManagerActivity extends AppCompatActivity {
private static final String TAG="BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ARRIVED=1;
private IBookManager mRemoteBookManager;
private Handler mHandler=new Handler(){
public void handleMessage( Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED:
Log.d(TAG, "received new book:"+msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private ServiceConnection mConnection=new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager=IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager=bookManager;
List<Book> list=bookManager.getBookList();
Log.i(TAG, "query book list,list type:"+list.getClass().getCanonicalName());
Log.i(TAG, "query book list:"+list.toString());
Book newBook=new Book(3,"Android开发艺术探索");
bookManager.addBook(newBook);
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 name) {
mRemoteBookManager=null;
Log.e(TAG, "binder died" );
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener=new IOnNewBookArrivedListener.Stub() {
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent=new Intent(this,BookManagerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
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();
}
}
RemoteCallbackList专为AIDL设计的方法
1
2
3
4
5//是系统专门提供的用于删除跨进程listener的接口
//自我清理:最核心功能。自动移除已死亡客户端的监听器,防止内存泄漏和异常。
//专用性:只用于 AIDL 跨进程回调,不是一个通用的 List。
//线程安全:所有操作(注册、注销、遍历)都是为多线程环境设计的。
//广播式遍历:必须使用 beginBroadcast/finishBroadcast 的固定模式来安全地通知所有监听器。给服务加上权限验证的功能
在onBind中进行验证
permission方法验证
1
2<permission android:name="com.example.ipctest.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>1
2
3
4
5
6
7
8
public IBinder onBind(Intent intent) {
int check=checkCallingOrSelfPermission("com.example.ipctest.permission.ACCESS_BOOK_SERVICE");
if(check== PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}如果内部应用想绑定这个服务
1
<user-permission android:name="com.example.ipctest.permission.ACCESS_BOOK_SERVICE"/>
在服务端的onTransact进行权限验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int check=checkCallingOrSelfPermission("com.example.ipctest.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=packages[0];
}
if(!packageName.startsWith("com.example")){
return false;
}
return super.onTransact(code, data, reply, flags);
}
ContentProvider
创建一个数据库
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
28public 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 static final String CREATE_BOOK_TABLE = "create table book("
+ "id integer primary key, "
+ "name text)";
private static final String CREATE_USER_TABLE="create table user("
+"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) {
}
}创建一个ContentProvider,并注册
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
106public class BookProvider extends ContentProvider {
private static final String TAG="BookProvider";
private static final String AUTHORITY="com.example.contentprovider.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 {
sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
}
private SQLiteDatabase db;
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;
}
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=db.delete(table,selection,selectionArgs);
if(count>0){
getContext().getContentResolver().notifyChange(uri,null);
}
return count;
}
public String getType( Uri uri) {
Log.d(TAG, "getType:");
return null;
}
public Uri insert( Uri uri, ContentValues values) {
Log.d(TAG, "insert: ");
String table=getTableName(uri);
if (table==null){
throw new IllegalArgumentException("Unsupported URI:"+uri);
}
db.insert(table,null,values);
getContext().getContentResolver().notifyChange(uri,null);
return uri;
}
public boolean onCreate() {
DbOpenHelper dbOpenHelper=new DbOpenHelper(getContext());
db=dbOpenHelper.getWritableDatabase();
//初始化数据库,这里仅用作演示,实际操作中不推荐在主线程执行耗时操作
initProviderData();
Log.d(TAG, "onCreate:,current thread:"+Thread.currentThread().getName());
return true;
}
private void initProviderData(){
db.execSQL("delete from "+DbOpenHelper.BOOK_TABLE_NAME);
db.execSQL("delete from "+DbOpenHelper.USER_TABLE_NAME);
db.execSQL("insert into book values(3,'Android');");
db.execSQL("insert into book values(4,'Ios');");
db.execSQL("insert into book values(5,'Html5');");
db.execSQL("insert into user values(1,'jake',1);");
db.execSQL("insert into user values(2,'jasmine',0);");
}
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(TAG, "onCreate:,current thread:"+Thread.currentThread().getName());
String table=getTableName(uri);
if(table==null){
throw new IllegalArgumentException("Unsupported URI:"+uri);
}
return db.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
}
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=db.update(table,values,selection,selectionArgs);
if(row>0){
getContext().getContentResolver().notifyChange(uri,null);
}
return row;
}
}1
2
3
4
5
6
7<provider
android:name=".BookProvider"
android:authorities="com.example.contentprovider.book.provider"
android:permission="com.example.PROVIDER"
android:process=":provider"
>
</provider>应用到Activity中
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
35public class MainActivity extends AppCompatActivity {
private static final String TAG="MainActivity";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Uri uri=Uri.parse("content://com.example.contentprovider.book.provider");
Uri bookUri=Uri.parse("content://com.example.contentprovider.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.example.contentprovider.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=userCursor.getInt(2)==1;
Log.d(TAG, "query user:"+user.toString());
}
userCursor.close();
}
}
Socket
服务端
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152/**
* 一个在后台运行的TCP服务器Service。
* <p>
* 这个服务启动后,会在8688端口上监听客户端的TCP连接请求。
* 它能够同时处理多个客户端的连接,并与每个客户端进行简单的随机消息交互。
*/
public class TCPServerService extends Service {
/**
* 标志位,用于表示Service是否已经被销毁。
* 当Service被销毁时,此标志位会变为true,从而通知所有正在运行的线程优雅地停止。
*/
private boolean mIsServiceDestoryed = false;
/**
* 预定义的消息数组,服务器会从中随机选择一条回复给客户端。
*/
private String[] mDefinedMessagges = new String[]{
"你好啊,哈哈",
"请问你叫什么名字呀?",
"今天北京天气不错呀,shy",
"你知道吗?我可是可以和多个人同时聊天的哦",
"给你讲个笑话吧,据说爱笑的人运气不会太差,不知道真假."
};
public TCPServerService() {
}
/**
* Service创建时调用,是服务的生命周期入口。
* 在这里,我们启动一个新的线程来运行TCP服务器的核心逻辑。
*/
public void onCreate() {
// 创建一个新的线程来运行TCPServer任务。
// 网络操作属于耗时操作,必须在子线程中进行,否则会阻塞UI主线程,导致ANR(应用无响应)。
new Thread(new TCPServer()).start();
super.onCreate();
}
/**
* 提供绑定服务的接口。
* @return 返回null表示这是一个非绑定的服务(Started Service),不与任何组件进行通信。
*/
public IBinder onBind(Intent intent) {
return null;
}
/**
* Service销毁时调用,是服务的生命周期终点。
* 在这里,我们将mIsServiceDestoryed标志位置为true,通知所有线程停止工作。
*/
public void onDestroy() {
mIsServiceDestoryed = true;
super.onDestroy();
}
/**
* 实现了Runnable接口的内部类,封装了TCP服务器的核心逻辑。
* 这个类的run方法将在一个独立的线程中执行。
*/
private class TCPServer implements Runnable {
public void run() {
ServerSocket serverSocket = null;
try {
// 在本地8688端口上创建并监听ServerSocket。
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.err.println("establish tcp server failed ,port:8688");
e.printStackTrace();
// 如果端口被占用或创建失败,则直接返回,线程结束。
return;
}
// 进入主循环,只要服务没有被销毁,就一直等待新的客户端连接。
while (!mIsServiceDestoryed) {
try {
// accept() 是一个阻塞方法,它会一直等待,直到有客户端连接成功。
// 连接成功后,它会返回一个代表该连接的Socket对象。
final Socket client = serverSocket.accept();
System.out.println("accept a new client");
// 【核心】为每一个新的客户端连接都创建一个新的线程去处理。
// 这样可以实现并发处理多个客户端,主线程可以立刻回去继续等待下一个连接。
new Thread() {
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (IOException e) {
// 在accept过程中也可能出现IO异常
e.printStackTrace();
}
}
}
}
/**
* 处理与单个客户端的所有通信。
* 这个方法会在一个专门为该客户端创建的线程中运行。
*
* @param client 代表与客户端连接的Socket通道。
* @throws IOException 在网络读写时可能会抛出IO异常。
*/
private void responseClient(Socket client) throws IOException {
// 用于从客户端读取数据。通过一系列包装,实现高效的按行读取。
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 用于向客户端发送数据。true参数表示开启自动行刷新,println后消息会立刻发送。
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
// 首先向客户端发送一条欢迎消息。
out.println("欢迎来到聊天室!");
// 进入通信循环,只要服务未被销毁并且客户端未断开连接,就一直持续。
while (!mIsServiceDestoryed) {
// readLine() 是一个阻塞方法,会等待客户端发送一行消息。
String str = in.readLine();
System.out.println("msg from client:" + str);
// 如果readLine()返回null,表示客户端的Socket已关闭,即连接已断开。
if (str == null) {
// 结束循环,准备关闭资源。
break;
}
// 从预设消息中随机选择一条进行回复。
int i = new Random().nextInt(mDefinedMessagges.length);
String msg = mDefinedMessagges[i];
out.println(msg);
System.out.println("send to client:" + msg);
}
System.out.println("client quit.");
// 【重要】当循环结束时,必须关闭所有资源。
// 先关闭输出流,再关闭输入流,最后关闭Socket。
// 如果不关闭,会造成资源泄漏。
// MyUtils.close是一个自定义的工具方法,用于安全地关闭流。
// 如果没有,可以直接调用 out.close(); in.close();
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177/**
* 这是一个TCP客户端界面 (Activity)。
* 它的功能是连接到后台的TCPServerService,并与之进行消息收发。
*/
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener {
// 定义Handler能够识别的消息类型:1 表示收到新消息
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
// 定义Handler能够识别的消息类型:2 表示Socket连接成功
private static final int MESSAGE_SOCKET_CONNECTED = 2;
// UI控件
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
// 网络通信相关
private PrintWriter mPrintWriter;// 用于向服务器发送消息
private Socket mClientSocket;// 客户端的Socket
/**
* Handler是Android中用于线程间通信的关键组件。
* 这里的Handler运行在主线程(UI线程)中,它可以接收来自任何子线程的消息,
* 然后安全地在主线程中更新UI界面。子线程绝对不能直接操作UI!
*/
private Handler handler = new Handler() {
public void handleMessage( Message msg) {
// 根据收到的消息类型(msg.what)来执行不同的操作
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG: {
// 如果是“收到新消息”,就把消息内容(msg.obj)追加到TextView上
mMessageTextView.append((String) msg.obj);
break;
}
case MESSAGE_SOCKET_CONNECTED: {
// 如果是“连接成功”,就把发送按钮变为可用状态
mSendButton.setEnabled(true);
break;
}
default:
break;
}
}
};
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载布局文件
setContentView(R.layout.activity_main);
// 初始化UI控件
mMessageTextView = (TextView) findViewById(R.id.textView);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this); // 设置按钮的点击监听器
mMessageEditText = (EditText) findViewById(R.id.editText);
// 启动后台TCP服务器服务
Intent service = new Intent(this, TCPServerService.class);
startService(service);
// 开启一个新的子线程来执行网络连接操作
// 因为连接服务器是耗时操作,必须在子线程中进行,防止UI卡顿
new Thread() {
public void run() {
connectTCPServer();
}
}.start();
}
/**
* Activity被销毁时调用。
* 在这里我们需要关闭Socket连接,释放资源。
*/
protected void onDestroy() {
if (mClientSocket != null) {
try {
// 关闭Socket的输入流和连接
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
/**
* 处理按钮点击事件。
*/
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
// 确保消息不为空,并且网络连接已准备好
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
// 【关键】发送消息是网络操作,必须在子线程中进行,否则会闪退
new Thread(new Runnable() {
public void run() {
mPrintWriter.println(msg);
}
}).start();
// 【关键】更新UI的操作(清空输入框、显示自己发送的消息)必须在主线程进行
mMessageEditText.setText("");
String time = formateDataTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
mMessageTextView.append(showedMsg);
}
}
}
/**
* 一个简单的时间格式化工具方法
*/
private String formateDataTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
/**
* 连接到TCP服务器的核心方法。
* 这个方法会在一个子线程中被调用。
*/
private void connectTCPServer() {
Socket socket = null;
// 使用循环来尝试连接服务器,直到连接成功为止
// 这样可以防止客户端比服务器先启动而导致连接失败
while (socket == null) {
try {
// 尝试连接本地(模拟器/真机自身)的8688端口
socket = new Socket("localhost", 8688);
mClientSocket = socket;
// 创建PrintWriter,用于向服务器发送消息
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
// 连接成功后,通过Handler向主线程发送“连接成功”的消息
handler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
// 如果连接失败,则等待1秒后重试
SystemClock.sleep(1000);
System.out.println("connect tcp server failed, retry...");
}
}
try {
// 连接成功后,准备接收服务器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 只要Activity没有被关闭,就一直循环读取服务器消息
while (!TCPClientActivity.this.isFinishing()) {
// readLine()是一个阻塞方法,会一直等待直到读取到一行消息
String msg = br.readLine();
System.out.println("receive :" + msg);
if (msg != null) {
// 收到消息后,格式化一下
String time = formateDataTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg + "\n";
// 通过Handler把收到的消息发送给主线程,让主线程去更新UI
handler.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();
}
}
}




