Android——进程间通信方式

Android——进程间通信方式1 IntentActivi Service Receiver 都支持在 Intent 中传递 Bundle 数据 而 Bundle 实现了 Parcelable 接口 可以在不同的进程间进行传输 在一个进程中启动了另一个进程的 Activity Service 和 Receiver 可以在 Bundle 中附加要传递的数据通过 Intent 发送出去 可以看看 Android Bundle 浅析 2 文件共享 Windows 上 一个文件如果被加了排斥锁会导致其他线程无法对其进行访问 包括读和

0. 前置知识

首先我们需要知道几点:RPC,IDL,IPC分别是什么。

RPC

  • Remote Procedure Call (远程过程调用) 是一种计算机通讯协议,为我们定义了计算机 C 中的程序如何调用另外一台计算机 S 的程序
  • RPC 是 Client/Server 模式,客户端对服务器发出请求,服务器收到请求并且根据客户端提供的参数进行操作,然后结果返回给客户端
  • RPC 位于 OSI 模型中的会话层
    在这里插入图片描述

IDL

  • Interface Description Language (接口定义语言),通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流
  • RPC 只是一种协议,规定了通信的规则
  • 因为客户端与服务端平台的差异性,为了统一处理不同的实现,需要定义一个共同的接口,即就是IDL

IPC

  • Inter-Process Communication (进程间通信)
  • Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”
    在这里插入图片描述

  • 只有允许不同应用的客户端用 IPC 方式调用远程方法,并且想要在服务中处理多线程时,才有必要使用 AIDL
  • 如果需要调用远程方法,但不需要处理并发 IPC,就应该通过实现一个 Binder 创建接口
  • 如果您想执行 IPC,但只是传递数据,不涉及方法调用,也不需要高并发,就使用 Messenger 来实现接口
  • 如果需要处理一对多的进程间数据共享(主要是数据的 CRUD),就使用 ContentProvider
  • 如果要实现一对多的并发实时通信,就使用 Socket

1.Intent

Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle实现了 Parcelable 接口,可以在不同的进程间进行传输。

在一个进程中启动了另一个进程的 Activity,Service 和 Receiver ,可以在Bundle 中附加要传递的数据通过 Intent 发送出去。

可以看看,Android——Bundle浅析

2. 文件共享

  • Windows 上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而 Android 系统基于 Linux ,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,尽管这样可能会出问题。
  • 可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同)
  • SharedPreferences 是个特例,系统对它的读 / 写有一定的缓存策略,即内存中会有一份ShardPreferences 文件的缓存,系统对他的读 / 写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很多大的几率丢失数据。因此,IPC 不建议采用 SharedPreferences

3. Messenger

Messenger是一种轻量级的 IPC 方案,它的底层实现是 AIDL ,可以在不同进程中传递 Message 对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。

服务端进程:服务端创建一个 Service 来处理客户端请求,同时通过一个Handler 对象来实例化一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。

public class MessengerService extends Service { 
    private static final String TAG = MessengerService.class.getSimpleName(); private class MessengerHandler extends Handler { 
    @Override public void handleMessage(Message msg) { 
    switch (msg.what) { 
    case Constants.MSG_FROM_CLIENT: Log.d(TAG, "receive msg from client: msg = ["+ msg.getData().getString(Constants.MSG_KEY) + "]"); Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show(); Messenger client = msg.replyTo; Message replyMsg = Message.obtain(null, Cons tants.MSG_FROM_SERVICE); Bundle bundle = new Bundle(); bundle.putString(Constants.MSG_KEY, "我已经收到你的消息,稍后回复你!"); replyMsg.setData(bundle); try { 
    client.send(replyMsg); } catch (RemoteException e) { 
    e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private Messenger mMessenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { 
    return mMessenger.getBinder(); } } 

客户端进程:首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。

public class MainActivity extends AppCompatActivity { 
    private static final String TAG = MainActivity.class.getSimpleName(); private Messenger mGetReplyMessenger = new Messenger(new MessageHandler()); private Messenger mService; private class MessageHandler extends Handler { 
    @Override public void handleMessage(Message msg) { 
    switch (msg.what) { 
    case Constants.MSG_FROM_SERVICE: Log.d(TAG, "received msg form service: msg =[" + msg.getData().getString(Constants.MSG_KEY) + "]"); Toast.makeText(MainActivity.this, "receivedmsg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } @Override protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void bindService(View v) { 
    Intent mIntent = new Intent(this, MessengerService.class); bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE); } public void sendMessage(View v) { 
    Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString(Constants.MSG_KEY, "Hello! This is client."); msg.setData(data); msg.replyTo = mGetReplyMessenger; try { 
    mService.send(msg); } catch (RemoteException e) { 
    e.printStackTrace(); } } @Override protected void onDestroy() { 
    unbindService(mServiceConnection); super.onDestroy(); } private ServiceConnection mServiceConnection = new ServiceConnection() { 
    / * @param name * @param service */ @Override public void onServiceConnected(ComponentName name, IBinder service) { 
    mService = new Messenger(service); Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString(Constants.MSG_KEY, "Hello! This is client."); msg.setData(data); msg.replyTo = mGetReplyMessenger; try { 
    mService.send(msg); } catch (RemoteException e) { 
    e.printStackTrace(); } } / * @param name */ @Override public void onServiceDisconnected(ComponentName name) { 
    } }; } 

注意:客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。只不过客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。Messenger 中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。

4. 使用 AIDL(难点)

AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。

AIDL底层也是通过Binder实现的:Android——Binder机制

Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。

AIDL 文件支持的数据类型:

  • 基本数据类型;
  • String 和 CharSequence
    在这里插入图片描述

  • ArrayList ,里面的元素必须能够被 AIDL 支持;
  • HashMap ,里面的元素必须能够被 AIDL 支持;
  • Parcelable ,实现 Parcelable 接口的对象; 注意:如果 AIDL 文件中用到了
    自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。

  • AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。

服务端:

客户端:

绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。

创建两个工程,一个作为服务端,一个作为客户端,客户端绑定服务端service,然后调用方法向服务端获取书籍列表,向服务端添加书籍。

1、服务端:
(1)创建aidl文件Book.aidl
在这里插入图片描述
创建后便可以在目录里看到aidl文件。
在这里插入图片描述
接下来定义Book类,注意Books类的包名必须与Book.aidl包名一样,但是不可与Book.aidl在同一个目录下。
在这里插入图片描述
Book.class的代码如下,其必须继承Parcelable接口:














package com.status.aidlproject.com.aidl; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { 
    private String bookName; public Book(String name) { 
    bookName = name; } public String getBookName() { 
    return bookName; } public void setBookName(String bookName) { 
    this.bookName = bookName; } protected Book(Parcel in) { 
    bookName = in.readString(); } public static final Creator<Book> CREATOR = new Creator<Book>() { 
    @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.writeString(bookName); } public void readFromParcel(Parcel desc) { 
    bookName = desc.readString(); } @Override public String toString() { 
    return "bookName=" + bookName; } } 

接下来修改Book.aidl文件,将其声明为parcelable类型,并且需要注意的是,原先默认的interface接口需要去掉,否则编译会报错。

// Book.aidl package com.status.aidlproject.com.aidl; // Declare any non-default types here with import statements parcelable Book; //需要删除 /*interface Book { void default(String s); }*/ 

接下来便是要定义服务端暴露给客户端的接口了(获取书籍列表,添加书籍)

// BookManager.aidl package com.status.aidlproject.com.aidl; // Declare any non-default types here with import statements import com.status.aidlproject.com.aidl.Book; interface { 
    List<Book> getBookList(); void addBook(inout Book book); } 

注意要把包手动导进来。

(2)创建Service,供客户端绑定

package com.status.aidlproject; import android.app.Service; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log; import com.status.aidlproject.com.aidl.Book; import com.status.aidlproject.com.aidl.BookManager; import java.util.ArrayList; import java.util.List; public class BookService extends Service { 
    private final String TAG = BookService.class.getSimpleName(); private List<Book> list; @Override public void onCreate() { 
    super.onCreate(); list = new ArrayList<>(); for (int i = 0; i < 5; i++) { 
    Book book = new Book("第" + i + "本书"); list.add(book); } } @Nullable @Override public IBinder onBind(Intent intent) { 
    return bookManager; } private BookManager.Stub bookManager = new BookManager.Stub() { 
    @Override public List<Book> getBookList() { 
    Log.d(TAG, "getBookList"); return list; } @Override public void addBook(Book book) { 
    Log.d(TAG, "addBook"); if (book != null) { 
    list.add(book); } Log.d(TAG, book.toString()); } }; @Override public void unbindService(ServiceConnection conn) { 
    super.unbindService(conn); } @Override public void onDestroy() { 
    super.onDestroy(); } } 

Manifests文件中可以这样写:

<service android:name=".BookService" android:enabled="true" android:exported="true" android:process=":remote"> <intent-filter> <action android:name="android.intent.action.BookService" /> </intent-filter> </service> 

onBind方法返回的是BookManager.Stub对象,实现里面的两个方法,客户端拿到这个对象后就可以与服务端通讯了。

2、客户端

package com.status.aidlclient; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; import com.status.aidlproject.com.aidl.Book; import com.status.aidlproject.com.aidl.BookManager; import com.status.aidlproject.com.aidl.IMyConnect; import java.util.List; public class MainActivity extends AppCompatActivity { 
    private final String TAG = MainActivity.class.getSimpleName();private TextView textView3; private TextView textView4; private TextView textView5; private final String ACTION1 = "android.intent.action.ConnectService"; private final String ACTION2 = "android.intent.action.BookService"; @Override protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView3 = findViewById(R.id.text3); textView4 = findViewById(R.id.text4); textView5 = findViewById(R.id.text5); textView3.setOnClickListener(new View.OnClickListener() { 
    @Override public void onClick(View v) { 
    Intent intent = new Intent(ACTION2); // 注意在 Android 5.0以后,不能通过隐式 Intent 启动 service,必须制定包名 intent.setPackage("com.status.aidlproject"); bindService(intent, connection2, Context.BIND_AUTO_CREATE); } }); textView4.setOnClickListener(new View.OnClickListener() { 
    @Override public void onClick(View v) { 
    try { 
    if (manager != null) { 
    List<Book> list = manager.getBookList(); Log.d(TAG, "getBookList:" + list); } } catch (RemoteException e) { 
    e.printStackTrace(); } } }); textView5.setOnClickListener(new View.OnClickListener() { 
    @Override public void onClick(View v) { 
    Book book = new Book("添加的书"); try { 
    if (manager != null) { 
    manager.addBook(book); } } catch (RemoteException e) { 
    e.printStackTrace(); } } }); } private BookManager manager; //个实现ServiceConnection接口 ServiceConnection connection2 = new ServiceConnection() { 
    @Override public void onServiceConnected(ComponentName name, IBinder service) { 
    Log.d(TAG, "onServiceConnected"); manager = BookManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { 
    manager = null; } }; } 

textView3用于绑定服务端

textView4用于获取服务端书籍列表

textView5用于向服务端添加书籍

5. 使用 ContentProvider

用于不同应用间数据共享,和 Messenger 底层实现同样是 Binder 和 AIDL,系统做了封装,使用简单。 系统预置了许多 ContentProvider ,如通讯录、日程表,需要跨进程访问。

使用方法:
继承 ContentProvider 类实现 6 个抽象方法,这六个方法均运行在 ContentProvider 进程中,除 onCreate 运行在主线程里,其他五个方法均由外界回调运行在 Binder 线程池中。

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); } @Override public void onCreate(SQLiteDatabase db) { 
    db.execSQL(CREATE_BOOK_TABLE); db.execSQL(CREATE_USER_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
    } } 

ContentProvider提供数据访问的接口, CRUD增删改查. 在onCreate中, 初始化数据库, 并添加数据.

@Override public boolean onCreate() { 
    showLogs("onCreate 当前线程: " + Thread.currentThread().getName()); mContext = getContext(); initProviderData(); // 初始化Provider数据 return false; } 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, 'Spike', 1);"); mDb.execSQL("insert into user values(2, 'Wang', 0);"); } 

CRUD的参数是Uri, 数据库需要使用表名, 为了便于从Uri映射到表名, 使用关系转换

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; } 

添加数据insert, 可以注册内容改变的监听, 插入数据时, 广播更新, 即notifyChange.

@Nullable @Override public Uri insert(Uri uri, ContentValues values) { 
    showLogs("insert"); String table = getTableName(uri); if (TextUtils.isEmpty(table)) { 
    throw new IllegalArgumentException("Unsupported URI: " + uri); } mDb.insert(table, null, values); // 插入数据后通知改变 mContext.getContentResolver().notifyChange(uri, null); return null; } 

删除数据delete, 返回删除数据的数量, 大于0即删除成功.

@Override public int delete(Uri uri, String selection, String[] selectionArgs) { 
    showLogs("delete"); String table = getTableName(uri); if (TextUtils.isEmpty(table)) { 
    throw new IllegalArgumentException("Unsupported URI: " + uri); } int count = mDb.delete(table, selection, selectionArgs); if (count > 0) { 
    mContext.getContentResolver().notifyChange(uri, null); } return count; // 返回删除的函数 } 

修改数据update, 与删除类似, 返回修改数据的数量.

@Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 
    showLogs("update"); String table = getTableName(uri); if (TextUtils.isEmpty(table)) { 
    throw new IllegalArgumentException("Unsupported URI: " + uri); } int row = mDb.update(table, values, selection, selectionArgs); if (row > 0) { 
    mContext.getContentResolver().notifyChange(uri, null); } return row; // 返回更新的行数 } 

查询数据query, 返回数据库的游标, 处理数据.

@Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { 
    showLogs("query 当前线程: " + Thread.currentThread().getName()); String tableName = getTableName(uri); if (TextUtils.isEmpty(tableName)) { 
    throw new IllegalArgumentException("Unsupported URI: " + uri); } return mDb.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null); } 

注意Uri和表名的转换可能为空, 使用TextUtils.isEmpty判空.

使用ContentProvider的独立进程, 模拟进程间共享数据.

<provider android:name=".BookProvider" android:authorities="org.wangchenlong.book.provider" android:permission="org.wangchenlong.BOOK_PROVIDER" android:process=":provider"/> 

在AndroidManifest中, 把Provider注册在:provider进程中, 与主进程分离.

添加数据, 通过Uri找到ContentProvider, 使用ContentResolver的insert方法, 添加ContentValues数据.

public void addBooks(View view) { 
    Uri bookUri = BookProvider.BOOK_CONTENT_URI; ContentValues values = new ContentValues(); values.put("_id", 6); values.put("name", "信仰上帝"); getContentResolver().insert(bookUri, values); } 

查询数据query, 与数据库的使用方式类似, 解析出Cursor, 通过移动Cursor, 找到所有匹配的结果.

public void showBooks(View view) { 
    String content = ""; Uri bookUri = BookProvider.BOOK_CONTENT_URI; Cursor bookCursor = getContentResolver().query(bookUri, new String[]{ 
   "_id", "name"}, null, null, null); if (bookCursor != null) { 
    while (bookCursor.moveToNext()) { 
    Book book = new Book(); book.bookId = bookCursor.getInt(0); book.bookName = bookCursor.getString(1); content += book.toString() + "\n"; Log.e(TAG, "query book: " + book.toString()); mTvShowBooks.setText(content); } bookCursor.close(); } } 

ContentProvider封装了跨进程共享的逻辑, 我们只需要Uri即可访问数据, 使用共享数据非常便捷, 需要掌握简单的使用方式.

6. Socket

  • Socket也称作“套接字“,是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。
  • 分为流式套接字和数据包套接字,分别对应网络传输控制层的TCP和UDP协议。
  • TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。它使用三次握手协议建立连接,并且提供了超时重传机制,具有很高的稳定性。
  • UDP协议则是是一种无连接的协议,且不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP传输的可靠性由应用层负责。在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多。

Socket 本身可以传输任意字节流。

谈到Socket,就必须要说一说 TCP/IP 五层网络模型:

  • 应用层:规定应用程序的数据格式,主要的协议 HTTP,FTP,WebSocket,POP3 等;
  • 传输层:建立“端口到端口” 的通信,主要的协议:TCP,UDP;
  • 网络层:建立”主机到主机”的通信,主要的协议:IP,ARP ,IP 协议的主要作用:一个是为每一台计算机分配 IP 地址,另一个是确定哪些地址在同一子网;
  • 数据链路层:确定电信号的分组方式,主要的协议:以太网协议;
  • 物理层:负责电信号的传输。
#include <sys/socket.h> #include <unistd.h> #include <unistd.h> int socket(int protofamily, int type, int protocol);//创建socket int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定socket int listen(int sockfd, int backlog);//监听端口号 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//客户端请求建立连接 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//服务端接收连接请求 ssize_t send(int sockfd, const void *buf, size_t len, int flags); //IO写函数 ssize_t recv(int sockfd, void *buf, size_t len, int flags);//IO读函数 int close(int fd); //关闭函数 

当我们使用socket来进行进程间的通信时,实际是通过将IP设置为127.0.0.1这个本地IP来实现的,Android系统为我们提供了LocalSocket来进行进程间的通信,LocalSocket的实质也是对Socket的封装,通过直接使用LocalSocket,我们省掉了设置本机IP等一系列繁琐的操作。

举个例子:
用Socket实现跨进程聊天程序

配置:

首先我们来实现服务端,当然要使用Socket我们需要在AndroidManifest.xml声明如下的权限:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 

我们需要实现一个远程的Service来当作聊天程序的服务端,AndroidManifest.xml文件中配置service:

<service android:name=".SocketServerService" android:process=":remote" /> 

实现Service:
接下来我们在Service启动时,在线程中建立TCP服务,我们监听的是8688端口,等待客户端连接,当客户端连接时就会生成Socket。通过每次创建的Socket就可以和不同的客户端通信了。当客户端断开连接时,服务端也会关闭Socket并结束结束通话线程。服务端首先会向客户端发送一条消息:“您好,我是服务端”,并接收客户端发来的消息,将收到的消息进行加工再返回给客户端。

package com.example.liuwangshu.moonsocket; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class SocketServerService extends Service { 
    private boolean isServiceDestroyed = false; @Override public void onCreate() { 
    new Thread(new TcpServer()).start(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { 
    // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } private class TcpServer implements Runnable { 
    @Override public void run() { 
    ServerSocket serverSocket; try { 
    //监听8688端口 serverSocket = new ServerSocket(8688); } catch (IOException e) { 
    return; } while (!isServiceDestroyed) { 
    try { 
    // 接受客户端请求,并且阻塞直到接收到消息 final Socket client = serverSocket.accept(); new Thread() { 
    @Override 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 (!isServiceDestroyed) { 
    String str = in.readLine(); Log.i("moon", "收到客户端发来的信息" + str); if (TextUtils.isEmpty(str)) { 
    //客户端断开了连接 Log.i("moon", "客户端断开连接"); break; } String message = "收到了客户端的信息为:" + str; // 从客户端收到的消息加工再发送给客户端 out.println(message); } out.close(); in.close(); client.close(); } @Override public void onDestroy() { 
    isServiceDestroyed = true; super.onDestroy(); } } 

实现聊天程序客户端:

客户端Activity会在onCreate方法中启动服务端,并开启线程连接服务端Socket。为了确保能连接成功,采用了超时重连的策略,每次连接失败时都会重新建立连接。连接成功后,客户端会收到服务端发送的消息:“您好,我是服务端”,我们也可以在EditText输入字符并发送到服务端。

package com.example.liuwangshu.moonsocket; import android.content.Intent; import android.os.SystemClock; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; public class SocketClientActivity extends AppCompatActivity { 
    private Button bt_send; private EditText et_receive; private Socket mClientSocket; private PrintWriter mPrintWriter; private TextView tv_message; @Override protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); setContentView(R.layout.activity_socket); initView(); Intent service = new Intent(this, SocketServerService.class); startService(service); new Thread() { 
    @Override public void run() { 
    connectSocketServer(); } }.start(); } private void initView() { 
    et_receive= (EditText) findViewById(R.id.et_receive); bt_send= (Button) findViewById(R.id.bt_send); tv_message= (TextView) this.findViewById(R.id.tv_message); bt_send.setOnClickListener(new View.OnClickListener() { 
    @Override public void onClick(View v) { 
    final String msg = et_receive.getText().toString(); //向服务器发送信息 if(!TextUtils.isEmpty(msg)&&null!=mPrintWriter) { 
    mPrintWriter.println(msg); tv_message.setText(tv_message.getText() + "\n" + "客户端:" + msg); et_receive.setText(""); } } }); } private void connectSocketServer() { 
    Socket socket = null; while (socket == null) { 
    try { 
    //选择和服务器相同的端口8688 socket = new Socket("localhost", 8688); mClientSocket = socket; mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true); } catch (IOException e) { 
    SystemClock.sleep(1000); } } try { 
    // 接收服务器端的消息 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (!isFinishing()) { 
    final String msg = br.readLine(); if (msg != null) { 
    runOnUiThread(new Runnable() { 
    @Override public void run() { 
    tv_message.setText(tv_message.getText() + "\n" + "服务端:" + msg); } } ); } } mPrintWriter.close(); br.close(); socket.close(); } catch (IOException e) { 
    e.printStackTrace(); } } } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/222734.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午3:06
下一篇 2026年3月17日 下午3:06


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号