Binder(一):IPC机制

Linux和Android的IPC机制

IPC机制全称为Inter-Process Communication,含义为进程间通信,指两个进程之间进行数据交换的过程,Linux和Android有各自的IPC机制


1. Linux的IPC机制

Linux中有很多进程间通信机制,如管道,信号,信号量,消息队列,共享内存,套接字等


管道

管道是Linux继承自UNIX的通信机制,主要思想是在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。这个共享文件比较特殊,不属于文件系统,并且只存在于内存里,管道采用半双工通信方式,数据只能在一个方向上进行流动


信号

信号是软件层次上对中断机制的一种模拟,信号是一种异步通信方式,进程不必通过任何操作来等待信号的到达,信号可以在用户空间进程和内核之间进行交换,内核可以利用信号来通知用户空间的进程发生了哪些系统事件。信号不适合于信息交换,比较适合于进程中断控制


信号量

信号量是一个计数器,用于控制多个进程对共享资源的访问,信号量常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源,信号量主要做为进程间及同一进程内不同线程间的同步手段


消息队列

消息队列是消息的链表,具有特定格式,消息队列存在在内存中由消息队列标识符进行标识,并且允许一个或多个进程向它写入与读取消息,使用消息队列会使信息复制两次,因此对于频繁通信或者信息量大的通信不宜使用消息队列


共享内存

共享内存的多个进程可以直接读写一块内存空间,是针对其他通信机制运行效率较低而设计的。为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间,这样,进程就可以直接读写这一块内存而不需要进行数据复制,提高效率


套接字

套接字则是更基础的进程间通信机制,与其他通信机制不同的是,套接字可用于不同机器之间的进程间通信


2. Android中的IPC机制

Android系统是基于Linux内核的,在Linux内核基础上又拓展出一些IPC机制,Android系统除了支持套接字,还支持序列化、Messenger、AIDL、Bundle、文件共享、ContentProvider和Binder等,先了解IPC机制


序列化

序列化是指Serializable / Parcelable接口,前者是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。后者是Android中的序列化操作,适合在Android平台上使用,效率高


Messenger

Messenger在Android应用开发中使用频率不高,可以在不同进程中传递Message对象,在Message中加入我们想要传递的数据就可以在进程间进行数据传递了,Messenger是一种轻量级的IPC方案,并对AIDL进行了封装


AIDL

AIDL全称是Android Interface Definition Language,即Android接口定义语言。Messenger是以串行的方式来处理客户端发来的信息的,如果有大量的消息发送到服务端,那么服务端仍然逐个处理再响应客户端显然是不合适的,虽然Messenger可以用于进程间数据传递,但是不能满足跨进程的方法调用,这个时候就需要AIDL


Bundle

Bundle实现了Parcelable接口,所以它可以在不同的进程间传输。Activity,Service,Receiver都是在Intent中通过Bundle来进行数据传递的


文件共享

两个进程通过读写同一个文件来进行数据共享,共享的文件可以是文本,XML,JSON。文件共享适用于对数据同步要求不高的进程间通信


ContentProvider

ContentProvider为存储和获取数据提供统一的接口,它可以在不同的应用程序之间共享数据,ContentProvider本身就是适合进程间通信的,ContentProvider底层实现也是Binder,但是使用起来比AIDL容易许多,系统中很多操作采用了ContentProvider,例如通讯录,音视频等,这些操作本身就是跨进程进行通信的


开启多进程

这一节主要展示一些代码示例,开启多进程。而开启多进程的原因主要有以下几点

  1. 单进程所分配的内存不够,需要更多内存。早期Android系统只为一个单进程应用分配了16MB的可用内存,随着手机硬件的提升和Android系统的改进,虽然可分配内存越来越多,但是仍旧可以通过开启多进程来获取更多内存处理APP的业务

  2. 独立运行的组件,比如个推,它的服务会开启另一个进程

  3. 运行一些私密操作,如获取用户隐私,防止双守护进程被用户杀掉等


1. 举例

我们在Activity中开启一个服务,如下

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent myServiceIntent = new Intent(MainActivity.this, MyService.class);
        this.startService(myServiceIntent);
    }
}

MyService

public class MyService extends Service {
    private static final String TAG = "TAG";

    @Override
    public void onCreate() {
        Log.i(TAG, "MyService is onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "MainActivity is created");
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG,"onDestroy");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

然后,在AndroidManifest文件中注册Service后添加android:process即可

AndroidManifest

<service 
            android:name=".MyService"
            android:label="@string/app_name"
            android:process=":remote"/>

这里remote是任意的,也可以为其他代号,remote前的冒号代替了当前应用的包名,所以MyService运行在名为com.example.xxx:remote进程中。我们也可以设置android:process="com.example.xxx.remote",这样MyService就运行在名为com.example.xxx.remote的进程中。这两种命名方式是有区别的

区别在于,如果被设置的进程名以“冒号”开头,则这个新的进程对这个应用来说是私有的,其他应用的组件不能和新的进程运行在同一进程中,当新的进程被需要或者这个服务需要在新进程中运行时,这个新进程将会被创建。如果被设置的进程名以小写“字母”开头,那这个服务将运行在一个以这个名字命名的全局进程中,当然前提是他有相应的权限,将允许不同应用中的组件运行在同一个进程中,从而减少资源占用

运行应用程序,我们可以看到有两个进程


image.png

2. 开启多进程的问题

开启多进程会使Application运行两次,我们继承自Application,在onCreate方法中添加log并运行程序

MyApplication

public class MyApplication extends Application {
    private static final String TAG = "TAG";

    @Override
    public void onCreate() {
        super.onCreate();
        int pid = android.os.Process.myPid();
        Log.i(TAG, "MyApplication is onCreate====" + "pid = " + pid);
    }
}

如果这样操作,开启的两个进程都会执行onCreate方法,现在很多开发者都喜欢在Application中进行初始化操作及数据传递,显然是不妥当的,解决的方法就是得到每个进程的名称,如果进程的名称和我们的应用进程名称相同,则进行应用的操作,如果不同则进行其他进程的操作,代码如下

MyApplication

public class MyApplication extends Application {
   private static final String TAG = "TAG";

   @Override
   public void onCreate() {
       super.onCreate();
       int pid = android.os.Process.myPid();
       Log.i(TAG, "MyApplication is onCreate====" + "pid = " + pid);
       String processNameString = " ";
       ActivityManager activityManager = (ActivityManager)this.getSystemService(getApplicationContext().ACTIVITY_SERVICE);
       for(ActivityManager.RunningAppProcessInfo appProcessInfo : activityManager.getRunningAppProcesses()){
           if(appProcessInfo.pid == pid){
               processNameString = appProcessInfo.processName;
           }
       }
       if("com.example.mybinder".equals(processNameString)){
           Log.i(TAG, "processName = " + processNameString + "----work");
       }else{
           Log.i(TAG, "processName = " + processNameString + "----work");
       }
   }
}

根据不同进程执行不同操作才是更合理的方式


用Messenger进行通信

Messenger可以在不同进程中传递Message对象,在Message中加入我们想要传递的数据就可以在进程间进行数据传递了。Messenger是一种轻量级的IPC方案,并对AIDL进行了封装,我们通过实例来看Messenger的工作原理

先写服务端,在onBind方法中创建Messenger,关联接收消息的Handler调用getBinder获取Binder对象,在handleMessage方法中接受客户端发来的消息

MessengerService

public class MessengerService extends Service {
    public static final String TAG = "TAG";
    public static final int MSG_FROMCLIENT = 1000;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message message){
            switch (message.what){
                case MSG_FROMCLIENT:
                    Log.i(TAG,"收到客户端信息-----" + message.getData().get("message"));
                    break;
            }
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new Messenger(mHandler).getBinder();
    }
}

然后在AndroidManifest文件里注册

AndroidManifest

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

接着创建客户端,绑定另一个进程服务,绑定成功后根据服务端返回的Binder对象创建Messenger,并用Messenger向服务端发送消息

MessengerActivity

public class MessengerActivity extends AppCompatActivity {

    private Messenger mMessenger;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Intent intent = new Intent(MessengerActivity.this, MessengerService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger = new Messenger(service);
            Message mMessage = Message.obtain(null, MessengerService.MSG_FROMCLIENT);
            Bundle mBundle = new Bundle();
            mBundle.putString("message", "这里是客户端,服务端收到了吗");
            mMessage.setData(mBundle);
            try{
                mMessenger.send(mMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }
}

运行后log显示如下


image.png

服务端收到了消息,但是服务端无法回应客户端,我们修改代码尝试让其可以回应

我们在handleMessage中收到客户端信息时,调用Message.replyTo得到客户端传递来的Messenger对象,创建消息并通过Messenger发送给客户端

MessengerService

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message message){
            switch (message.what){
                case MSG_FROMCLIENT:
                    Log.i(TAG,"收到客户端信息-----" + message.getData().get("message"));
                    Messenger messenger = message.replyTo;
                    Message mMessage = Message.obtain(null, MessengerService.MSG_FROMCLIENT);
                    Bundle bundle = new Bundle();
                    bundle.putString("rep", "这里是服务端,我们收到信息了");
                    mMessage.setData(bundle);
                    try {
                        messenger.send(mMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    };

然后修改客户端,客户端需要创建一个Handler接受消息,并且关联Messenger对象

MessengerActivity

private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger = new Messenger(service);
            Message mMessage = Message.obtain(null, MessengerService.MSG_FROMCLIENT);
            Bundle mBundle = new Bundle();
            mBundle.putString("message", "这里是客户端,服务端收到了吗");
            mMessage.setData(mBundle);
            mMessage.replyTo = new Messenger(mHandler);
            try{
                mMessenger.send(mMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private Handler mHandler = new Handler(){
      @Override
      public void handleMessage(Message msg){
          switch (msg.what){
              case MessengerService.MSG_FROMCLIENT:
                  Log.i(MessengerService.TAG, "收到服务端信息------" + msg.getData().get("rep"));
                  break;
          }
      }
    };

运行代码查看log如下


image.png

这样双向通信就完成了


用AIDL实现跨进程的方法调用

上面我们发现Messenger是串行处理客户端消息的,如果有大量的消息发送到服务端,服务端仍然逐个处理再响应客户端是不合适的。虽然Messenger用来实现进程间数据传递,但是却不能满足跨进程的方法调用,但AIDL可以


1. 创建AIDL文件

将预览模式改为Android,在Java同级目录下创建AIDL文件夹,新建一个包,包名和应用包名一致。创建一个IGameManager文件,里面有两个方法,一个addGame,一个getGameList


image.png

IGameManager

interface IGameManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     
     List<Game> getGameList();
     void addGame(in Game game);
}

AIDL中支持基本数据类型,String和CharSequence,ArrayList,HashMap,实现Parcelable接口的对象,以及所有AIDL接口。在IGameManager.aidl中我们用到了Game类,这个类实现了Parcelable接口,在AIDL文件中我们需要import来查看Game类

Game

public class Game implements Parcelable {
    public String gameName;
    public String gameDescribe;
    public Game(String gameName, String gameDescribe){
        this.gameDescribe = gameDescribe;
        this.gameName = gameName;
    }

    protected Game(Parcel in){
        gameName = in.readString();
        gameDescribe = in.readString();
    }

    public static final Creator<Game> CREATOR = new Creator<Game>() {
        @Override
        public Game createFromParcel(Parcel in) {
            return new Game(in);
        }

        @Override
        public Game[] newArray(int size) {
            return new Game[size];
        }
    };

    @Override
    public int describeContents(){
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags){
        dest.writeString(gameName);
        dest.writeString(gameDescribe);
    }
}

在上面的IGameManager.aidl文件中我们用到了Game类,所以要创建Game.aidl来申明Game类实现了Parcelabe接口

Game

package com.example.mybinder;

// Declare any non-default types here with import statements
parcelable Game;

重新编译程序,工程就会自动生成IGameManger.aidl对应的接口文件,这个文件在Project模式下的generated/source/aidl/debug目录中


image.png

2. 创建服务端

在服务端onCreate方法中创建了两个游戏信息,并创建Binder对象实现了AIDL的接口文件中的方法,在onBind方法中将Binder对象返回

AIDLService

public class AIDLService extends Service {
    private CopyOnWriteArrayList<Game> mGameList = new CopyOnWriteArrayList<>();
    private Binder binder = new IGameManager.Stub(){

        @Override
        public List<Game> getGameList() throws RemoteException {
            return mGameList;
        }

        @Override
        public void addGame(Game game) throws RemoteException {
            mGameList.add(game);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mGameList.add(new Game("九阴真经","最好玩的武侠网游"));
        mGameList.add(new Game("大航海时代","最好玩的航海手游"));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

当然这个服务端应该运行在另一个进程,所以还要注册一下

AndroidManifest

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

3. 客户端调用

最后在客户端onCreate方法中调用bindService方法绑定远程服务端,绑定成功后将返回的Binder对象转换为AIDL接口,这样就可以通过接口来调用远程服务端的方法了

AIDLActivity

public class AIDLActivity extends AppCompatActivity {
    private final static String TAG = "TAG";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidlactivity);
        Intent intent = new Intent(AIDLActivity.this, AIDLService.class);
        bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IGameManager iGameManager = IGameManager.Stub.asInterface(service);
            Game game = new Game("月影传说","最好玩的武侠单机");
            try{
                iGameManager.addGame(game);
                List<Game> mList = iGameManager.getGameList();
                for(int i = 0; i < mList.size(); i++){
                    Game mGame = mList.get(i);
                    Log.i(TAG, mGame.gameName + "----" + mGame.gameDescribe);
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }
}

绑定成功后,我们创建了一个新的Game类调用远程服务端的addGame方法,添加游戏后进行循环打印,结果如下log


image.png

这样就成打印了所有游戏,在客户端通过AIDL来调用远程服务端的方法


用ContentProvider进行进程间通信

ContentProvider为存储和获取数据提供统一的接口,可以在不同的应用程序间共享数据,本身就是适合多进程通信的,其底层实现也是Binder,但是使用较AIDL更方便


1. 建立数据库

创建数据库表“game_provider.db”,里面有两个字段,分别用于存储游戏名和游戏描述

DbOpenHelper

public class DbOpenHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "game_provider.db";
    static final String GAME_TABLE_NAME = "game";
    private static final int DB_VERSION = 1;
    private String CREATE_GAME_TABLE = "create table if not exists " + GAME_TABLE_NAME + "(_id integer primary key, " + "name Text, " + "describe Text)";
    public DbOpenHelper(Context context){
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_GAME_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        
    }
}

2. 使用ContentProvider对数据库进行操作

在initProvider方法中,开启线程对数据库进行操作,删除表的所有数据,再添加我们想要的数据,接着实现query和insert方法

public class GameProvider extends ContentProvider {
    public static final String AUTHORITY = "com.example.mybinder.GameProvider";
    public static final Uri GAME_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/game");
    private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private SQLiteDatabase mDb;
    private Context context;
    private String table;
    static {mUriMatcher.addURI(AUTHORITY, " game", 0);}
    @Override
    public boolean onCreate() {
        table = DbOpenHelper.GAME_TABLE_NAME;
        context = getContext();

        return false;
    }

    private void initProvider(){
        mDb = new DbOpenHelper(context).getWritableDatabase();
        new Thread(new Runnable() {
            @Override
            public void run() {
                mDb.execSQL("delete from " + DbOpenHelper.GAME_TABLE_NAME);
                mDb.execSQL("insert into game values(1, '九阴真经ol','最好玩的武侠网游');");
            }
        }).start();
    }
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        String table = DbOpenHelper.GAME_TABLE_NAME;
        Cursor cursor = mDb.query(table, projection, selection, selectionArgs, null, sortOrder, null);
        return cursor;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        mDb.insert(table, null, values);
        context.getContentResolver().notifyChange(uri, null);
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

同样,身为四大组件之一,也要注册

AndroidManifest

<provider
            android:authorities="com.example.mybinder.GameProvider"
            android:name=".GameProvider"
            android:process=":provider"/>

3. 在Activity中调用另一个进程的GameProvider方法

在GameProvider中插入一条数据,然后调用GameProvider的query方法来查询数据库中有几条数据并打印

ContentProviderActivity

public class ContentProviderActivity extends AppCompatActivity {
    private static final String TAG = "TAG";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        Uri uri = Uri.parse("content://com.example.mybinder.GameProvider");
        ContentValues contentValues = new ContentValues();
        contentValues.put("_id", 2);
        contentValues.put("name","大航海时代");
        contentValues.put("describe","最好玩的航海网游");
        getContentResolver().insert(uri, contentValues);
        Cursor gameCursor = getContentResolver().query(uri, new String[]{"name", "describe"},null,null,null);
        while (gameCursor.moveToNext()){
            Game mGame = new Game(gameCursor.getString(0), gameCursor.getString(1));
            Log.i(TAG, mGame.gameName + "---" + mGame.gameDescribe);
        }
        
    }
}

Bean文件我们在之前写过了,直接使用上面的Game文件即可

Game

public class Game implements Parcelable {
    public String gameName;
    public String gameDescribe;
    public Game(String gameName, String gameDescribe){
        this.gameDescribe = gameDescribe;
        this.gameName = gameName;
    }

    protected Game(Parcel in){
        gameName = in.readString();
        gameDescribe = in.readString();
    }

    public static final Creator<Game> CREATOR = new Creator<Game>() {
        @Override
        public Game createFromParcel(Parcel in) {
            return new Game(in);
        }

        @Override
        public Game[] newArray(int size) {
            return new Game[size];
        }
    };

    @Override
    public int describeContents(){
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags){
        dest.writeString(gameName);
        dest.writeString(gameDescribe);
    }
}

最后打印log显示我们使用ContentProvider对数据库完成了操作


image.png

使用Socket实现跨进程聊天程序

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


image.png

1. 配置

开启网络权限

AndroidManifest

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

另外,需要实现一个远程的Service来当作聊天程序的服务端

AndroidManifest

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

2. 实现Service

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

SocketServerService

public class SocketServerService extends Service {
    private boolean isServiceDestroyed = false;

    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException();
    }

    private class TcpServer implements Runnable{
        @Override
        public void run() {
            ServerSocket serverSocket;
            try {
                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("TAG", "收到客户端的信息" + str);
            if(TextUtils.isEmpty(str)){
                Log.i("TAG","客户端断开链接");
                break;
            }
            String message = "收到了客户端的信息为:" + str;
            // 从客户端收到的消息加工再发送给客户端
            out.println(message);
        }
        out.close();
        in.close();
        client.close();
    }

    @Override
    public void onDestroy() {
        isServiceDestroyed = true;
        super.onDestroy();
    }
}

3. 实现聊天程序客户端

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

SocketClientActivity

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_client);
        initView();
        Intent service = new Intent(this, SocketServerService.class);
        startService(service);
        //防止报android.os.NetworkOnMainThreadException异常
        if (android.os.Build.VERSION.SDK_INT > 9) {
            StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
        }
        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(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(() -> tv_message.setText(tv_message.getText() + "\n" + "服务端:" + msg)
                    );
                }
            }
            mPrintWriter.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4. 布局

activity_socket_client

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_message"
        android:layout_width="match_parent"
        android:layout_height="400dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_receive"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            />

        <Button
            android:id="@+id/bt_send"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="向服务器发消息" />
    </LinearLayout>
</RelativeLayout>

运行后,我们可以看到是开启了两个进程的


image.png

实际使用时,也可以看到我们是可以和服务端进行通信的


image.png

IPC机制就介绍到这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容