• Android开发艺术探索读书笔记——进程间通信


    1. 多进程使用场景

    1) 应用某些模块由于特殊需求须要执行在单独进程中。

    如消息推送,使消息推送进程与应用进程能单独存活,消息推送进程不会由于应用程序进程crash而受影响。
    2) 为加大一个应用可使用的内存。须要多进程来获取多份内存空间。

    2. 怎样开启多进程

    给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMainfest中指定android:process属性指定。

    • 假设进程以”:”开头的进程。代表应用的私有进程,其它应用的组件不能够和它跑在同一个进程中。
    • 进程不以”:”开头的进程属于全局进程。其它应用可通过shareUID能够和它跑在同一个进程中。
    • 若两个不同应用设置了同样的shareUID,它们之间想共享数据,还须要有同样的签名才干够。

    3. 多进程会造成的问题

    (1) 静态成员和单例失效,数据同步失败;
    Android会为每个应用/每个进程分配一个独立的虚拟机。不同虚拟机在内存分配上有不同的地址空间,这就导致不同虚拟机中訪问同一个类对象会产生多个副本。


    (2) 线程同步机制失效。
    由于不同进程不是同一块内存,不同进程锁的不是同一对象。
    (3) SharedPreferences可靠性下降;
    SharedPreferences底层是通过读/写XML文件实现的。并发写可能会出问题。所以它不支持多个进程同一时候去执行写操作。否则会导致一定几率的数据丢失。
    (4) Application会创建多次。
    当一个组件跑在一个新进程中,系统会给它又一次分配独立虚拟机,这事实上就是启动一个应用的过程,故执行在不同进程中的组件属于不同的虚拟机和不同的Application。

    4. 数据序列化

    Intent和Binder数据传输时。或是对象持久化转存/通过网络传输给其它client时,须要使用Parcelable或Serializable将对象转换成能够传输的形式。


    (1) Serializable接口
    Serializable是Java提供的一个序列化接口,它是一个空接口。想要某个类实现序列化,仅仅须要对应类实现Serializable接口就可以。Serializable是借助ObjectOutputStream和ObjectInputStream实现对象的序列化和反序列化
    一般在对应类实现Serializable类中还会定义一个final long型的serialVersionUID,不用它也能实现对象的序列化。它是用来辅助反序列化的。

    序列化时会把当前类的serialVersionUID写入序列化文件里。当反序列化系统会检測文件里的serialVersionUID,看它是否和当前类的serialVersionUID一致,假设一致说明序列化的类的版本号和当前类的版本号同样,这时可反序化成功;否则说明当前类和序列化的类相比发生了变换,如添加或降低某个成员变量,这时无法正常反序列化。
    (2) Parcelable接口
    在序列化过程中须要实现的功能有:
    1) 序列化:由writeToParcel方法完毕,终于通过Parcel中的一系列的write方法完毕。
    2) 反序列化:由CREATOR完毕。内部标识了怎样创建序列化对象和数组,终于通过Parcel的一系列read方法来完毕反序列化;
    3) 内容描写叙述符:由describeContents方法来完毕。差点儿全部情况下该方法都返回0,仅当当前对象中存在文件描写叙述符时返回1。
    (3)两者差别
    Serializable是Java中序列化接口,使用简单但开销大,序列和反序列化须要大量I/O操作。
    Parcelable是Android中特有的序列化接口,使用复杂但开销小,效率高,是Android推荐的序列化方法;
    Parcelable主要用在内存序列化上,假设将对象序列化到存储设备或将对象序列化后通过网络传输。过程会比較复杂。建议使用Serializable。

    5. Binder

    Binder是什么?

    • Binder是Android中的一个类,它继承了IBinder接口;
    • 从IPC角度来说,Binder是Android中一种跨进程通信的方式;
    • Binder还能够理解为一种虚拟的物理设备,它的设备驱动是/dev/binder。
    • 从AndroidFramework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager, WindowManager)和对应ManagerService的桥梁;
    • 从Android应用层来说。Binder是client和服务端进行通信的媒介,当bindService时,服务端会返回一个包括了服务端业务的Binder对象,通过这个Binder对象,client就能够获取服务端提供的服务或数据。这里的服务包括普通服务和基于AIDL的服务。

    6.android中跨进程通信方式

    6.1 结合Bundle使用Intent

    在一个进程中启动还有一个进程的Activity, Service, Receiver组件时,能够使用Bundle附加上须要传递的消息给远程进程。并通过Intent发送出去。

    6.2 使用文件共享

    两个进程通过读/写同一个文件来交换数据,还能够序列化一个对象到文件系统中。从还有一个进程中恢复这个对象。它的实现原理是通过ObjectOutputStream将文件写入文件里。再通过ObjectInputSteam将文件恢复。

    通过文件共享的方式有一定局限性。如并发读/写,读出的内容可能不是最新的。并发写就可能导致数据混乱。因此尽量避免并发写这样的操作,或考虑用线程同步来限制多个线程的写操作。文件共享适合在对数据要求不高的进程之间通信。


    SharedPreferences是Android提供的一种轻量级存储方案,它通过键值对方式存储数据,底层它是採用XML来存储键值对。

    由于系统对SharedPreferences的读写有一定的缓存策略。即在内存中会有一份SharedPreferences文件的缓存,在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发读/写訪问时。SharedPreferences会有非常大几率会丢失数据。因此不建议在进程间通信中使用SharedPreferences。

    6.3 使用Messenger

    Messenager能够在不同进程中传递Message对象。在Message中放入我们要传递的数据。

    它的底层实现是AIDL,以下是Messenger的两个构造方法:

    public Messenger(Handler target) {
            mTarget = target.getIMessenger();
        }
    public Messenger(IBinder target) {
            mTarget = IMessenger.Stub.asInterface(target);
        }

    无论是IMessenger还是Stub.asInterface,这样的用法都表明它的底层是AIDL,它一次仅仅处理一个请求,不存在线程同步问题。
    实现步骤:
    (1) 服务端进程
    1) 定义一个Service用于client的绑定,建立一个Handler,在handleMessage里处理client发送过来的消息。

    //用ServiceHandler接收并处理来自于client的消息
        private class ServiceHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                if(msg.what == RECEIVE_MESSAGE_CODE){
                    Bundle data = msg.getData();
                    if(data != null){
                        String str = data.getString("msg");
                    }
                    //通过Message的replyTo获取到client自身的Messenger,
                    //Service能够通过它向client发送消息
                    clientMessenger = msg.replyTo;
                    if(clientMessenger != null){
                        Message msgToClient = Message.obtain();
                        msgToClient.what = SEND_MESSAGE_CODE;
                        //能够通过Bundle发送跨进程的信息
                        Bundle bundle = new Bundle();
                        bundle.putString("msg", "你好,client,我是MyService");
                        msgToClient.setData(bundle);
                        try{
                            clientMessenger.send(msgToClient);
                        }catch (RemoteException e){
                            e.printStackTrace();
                            Log.e("DemoLog", "向client发送信息失败: " + e.getMessage());
                        }
                    }
                }
            }
        }

    2) 通过Handler创建一个Messenger对象

    //serviceMessenger是Service自身的Messenger,其内部指向了ServiceHandler的实例
        //client能够通过IBinder构建Service端的Messenger,从而向Service发送消息,
        //并由ServiceHandler接收并处理来自于client的消息
        private Messenger serviceMessenger = new Messenger(new ServiceHandler());

    3) 在Service的onBind中通过Messenger.getBinder()返回底层的Binder对象。

    @Override
        public IBinder onBind(Intent intent) {
            Log.i("DemoLog", "MyServivce -> onBind");
            //获取Service自身Messenger所对应的IBinder。并将其发送共享给全部client
            return serviceMessenger.getBinder();
        }

    4) 注冊服务,让其执行在单独进程中

    <service
        android:name=".MyService"
        android:enabled="true"
        android:process=":remote" >
    </service>

    (2) client进程
    1) 绑定服务端的Service

     private ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder binder) {
                //client与Service建立连接
                Log.i("DemoLog", "client onServiceConnected");
    
                //我们能够通过从Service的onBind方法中返回的IBinder初始化一个指向Service端的Messenger
                serviceMessenger = new Messenger(binder);
                isBound = true;
    
                Message msg = Message.obtain();
                msg.what = SEND_MESSAGE_CODE;
    
                //此处跨进程Message通信不能将msg.obj设置为non-Parcelable的对象,应该使用Bundle
                //msg.obj = "你好。MyService,我是client";
                Bundle data = new Bundle();
                data.putString("msg", "你好,MyService,我是client");
                msg.setData(data);
    
                //须要将Message的replyTo设置为client的clientMessenger,
                //以便Service能够通过它向client发送消息
                msg.replyTo = clientMessenger;
                try {
                    Log.i("DemoLog", "client向service发送信息");
                    serviceMessenger.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                    Log.i("DemoLog", "client向service发送消息失败: " + e.getMessage());
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //client与Service失去连接
                serviceMessenger = null;
                isBound = false;
                Log.i("DemoLog", "client onServiceDisconnected");
            }
        };
    
        Intent intent = new Intent();            
        ComponentName componentName = new ComponentName(packageName, serviceNmae);
        intent.setComponent(componentName);
        try{
            Log.i("DemoLog", "client调用bindService方法");
            bindService(intent, conn, BIND_AUTO_CREATE);
        }catch(Exception e){
            e.printStackTrace();
            Log.e("DemoLog", e.getMessage());
        }
    }

    绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过它向服务端发送Message消息。


    假设须要服务端给client发送消息。须要在Handler的handleMessage方法里,依据client发送过来的Message.replyTo获取到client的Messenger对象,就能够向client发送消息了。同一时候client须要在服务连接的onServiceConnected方法中,将client的Messenger对象通过Message.replyTo给收到服务端发送过来的Message对象。这样就实现两方通信。

    6.4 AIDL

    6.4.1 在服务端新建须要的AIDL类

    (1)先新建一个Book.java实现Parcelable接口,表示一个图书的信息类;

    import android.os.Parcel;
    import android.os.Parcelable;
    
    /**
     * Book.java
     */
    public class Book implements Parcelable{
        private String name;
        private int price;
        public Book(){}
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getPrice() {
            return price;
        }
    
        public void setPrice(int price) {
            this.price = price;
        }
    
        public Book(Parcel in) {
            name = in.readString();
            price = in.readInt();
        }
    
        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(name);
            dest.writeInt(price);
        }
    
        /**
         * 參数是一个Parcel,用它来存储与数据传输
         * @param dest
         */
        public void readFromParcel(Parcel dest) {
            //注意。此处的读值顺序应当是和writeToParcel()方法中一致的
            name = dest.readString();
            price = dest.readInt();
        }
    
        //方便打印数据
        @Override
        public String toString() {
            return "name : " + name + " , price : " + price;
        }
    }

    (2) 新建一个Book.aidl。表示Book类在AIDL中的声明;

    // Book.aidl
    //这个文件的作用是引入了一个序列化对象 Book 供其它的AIDL文件使用
    //注意:Book.aidl与Book.java的包名应当是一样的
    //注意parcelable是小写
    parcelable Book;

    (3) 新建一个IBookManger.aidl接口,里面包括对应的方法;

    // IBookManger.aidl
    //导入所须要使用的非默认支持数据类型的包
    import com.lypeer.ipcclient.Book;
    
    interface IBookManger {
    
        //全部的返回值前都不须要加不论什么东西。无论是什么数据类型
        List<Book> getBooks();
        Book getBook();
    }

    该方法保存后,会在gen文件夹下生成一个IBookManger.java类,它是系统为BookManger.aidl生成的Binder类,继承android.os.IInterface。它自己也还是个接口。
    它包括以下内容:
    1) 定义了一个String型的DESCRIPTOR。Binder的唯一标识;
    2) asInterface(android.os.IBinder obj)方法,将服务端的Binder对象转换成client所需的AIDL接口类型对象,当client和服务端处于同一进程,直接返回服务端的Stub对象本身。否则返回系统封装后的Stub.proxy对象。


    3) asBinder方法:返回当前Binder对象。
    4) onTransact方法:执行在服务端的Binder线程池中,当client发起远程请求,远程请求会通过系统封装后交由此方法处理,该方法原型为public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端通过code能够确定client请求的是哪一个方法,从data中取出目标方法所需的參数,执行对应方法后,将返回值写入reply中。假设此方法返回false,client会请求失败。
    5) IBookManger.aidl接口中声明的方法的代理实现,此方法执行在client,当client调用此方法时,首先须要创建此方法须要的输入类型Parcel对象_data, 输出类型Parcel对象_reply和返回值对象。接着将參数信息写入_data中(若有參数的话),再调用transact方法发起RPC(远程过程调用)。当前线程挂起,服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行。并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。
    6) 声明了IBookManger.aidl中声明的方法。
    7) 声明了对应的整型id分别用于标识声明的方法。该id用于标识在transact过程中推断client请求的究竟是哪个方法。
    8) 声明了一个类部类Stub,继承android.os.Binder实现IBookManager.aidl中类接口。当client和服务端处于同一进程,方法调用不会走跨进程的transact过程。若位于不同进程。则由Stub的内部代理Proxy,调用跨进程transact过程。


    须要注意的是:client发起请求时。当前线程会挂起直至服务端返回数据,若方法是一个耗时操作,不能在UI线程中发起此远程请求。服务端的Binder方法执行在Binder线程池中。所以无论操作是否耗时,都应採用同步的方法去实现。
    (4)linkToDeath unlinkToDeath
    由于Binder执行在服务端进程中。假设服务端进程由于某种原因终止。这时client服务端的Binder连接断裂,会导致远程调用失败。但client非常可能不知道,解决此问题的办法,利用Binder提供的两个配对方法linkToDeath和unlinkToDeath,通过linkToDeath能够给Binder设置一个死亡代理,当Binder被终止时。我们会接收到通知。这时可通过又一次发起请求恢复连接。
    实现方法:
    1) 先声明一个DeathRecipient对象,它是一个接口。内部仅仅有一个方法binderDied,我们须要实现该方法,当Binder死亡时,系统回调binderDied方法,我们能够移出之前binder代理并又一次建立远程服务。

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                if (mBookManager == null) {
                    return;
                }
                mBookManger.asBinder().unlinkToDeath(mDeathRecipient, 0);
                mBookManger = null;
                //又一次绑定远程服务
            }
        };

    2) 在client绑定远程服务成功后,给binder设置死亡代理;

    mService = IBookManager.Stub.asInterface(binder);
    binder.linkToDeath(mDeathRecipient, 0);

    6.4.2 远程服务端Serivce的实现

    (1)Serivce建立
    我们须要新建一个Service,称为AIDLService,代码例如以下:

    /**
     1. 服务端的AIDLService.java
     */
    public class AIDLService extends Service {
    
        public final String TAG = this.getClass().getSimpleName();
    
        //包括Book对象的list
        private List<Book> mBooks = new ArrayList<>();
    
        //由AIDL文件生成的IBookManager
        private final IBookManager.Stub mBookManager = new IBookManager.Stub() {
            @Override
            public List<Book> getBooks() throws RemoteException {
                synchronized (this) {
                    Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
                    if (mBooks != null) {
                        return mBooks;
                    }
                    return new ArrayList<>();
                }
            }
    
    
            @Override
            public void addBook(Book book) throws RemoteException {
                synchronized (this) {
                    if (mBooks == null) {
                        mBooks = new ArrayList<>();
                    }
                    if (book == null) {
                        Log.e(TAG, "Book is null in In");
                        book = new Book();
                    }
                    //尝试改动book的參数,主要是为了观察其到client的反馈
                    book.setPrice(2333);
                    if (!mBooks.contains(book)) {
                        mBooks.add(book);
                    }
                    //打印mBooks列表,观察client传过来的值
                    Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
                }
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            Book book = new Book();
            book.setName("Android开发艺术探索");
            book.setPrice(28);
            mBooks.add(book);   
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
            return mBookManager;//在onBinder中返回服务端的Binder对象
        }
    }

    (2)注冊服务

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

    6.4.3 client的实现

    /**
     2. client的AIDLActivity.java
     */
    public class AIDLActivity extends AppCompatActivity {
    
        //由AIDL文件生成的Java类
        private IBookManager mBookManager = null;
    
        //标志当前与服务端连接状况的布尔值,false为未连接。true为连接中
        private boolean mBound = false;
    
        //包括Book对象的list
        private List<Book> mBooks;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_aidl);
        }
    
        /**
         * button的点击事件。点击之后调用服务端的addBookIn方法
         *
         * @param view
         */
        public void addBook(View view) {
            //假设与服务端的连接处于未连接状态,则尝试连接
            if (!mBound) {
                attemptToBindService();
                Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
                return;
            }
            if (mBookManager == null) return;
    
            Book book = new Book();
            book.setName("APP研发录In");
            book.setPrice(30);
            try {
                mBookManager.addBook(book);//通过服务端的Binder对象调服务端方法
                Log.e(getLocalClassName(), book.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 尝试与服务端建立连接
         */
        private void attemptToBindService() {
            Intent intent = new Intent();
            intent.setAction("aidl.AIDLService");
            intent.setPackage("com.xx.packagename");
            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);//绑定服务
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            if (!mBound) {
                attemptToBindService();
            }
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            if (mBound) {
                unbindService(mServiceConnection);
                mBound = false;
            }
        }
    
        private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.e(getLocalClassName(), "service connected");
                mBookManager = IBookManager.Stub.asInterface(service);
                mBound = true;
    
                if (mBookManager != null) {
                    try {
                        mBooks = mBookManager.getBooks();//调用服务端的getBooks方法
                        Log.e(getLocalClassName(), mBooks.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e(getLocalClassName(), "service disconnected");
                mBound = false;
            }
        };
    }

    通过获取服务端的Binder对象,就能够和服务端进行通信了。

    6.4.4 其它知识点

    (1)跨进程listener接口
    假设在服务端定声明了接口,client进行注冊和反注冊。会抛出异常,无法反注冊。由于在多进程中,Binder会把client传过来的对象又一次转化成一个新对象,这样尽管client注冊和反注冊使用的是同一个对象,可是通过Binder传递到服务端却变成两个对象。对象的跨进程传输本质都是反序列化过程。
    能够使用RemoteCallbackList,它是系统专门用来删除跨进程listener接口。它实现原理利用底层Binder对象是同一个,仅仅要遍历服务端全部listener,找出和解注冊listener具有同样Binder对象的服务端listener,并把它删除掉。

    同一时候RemoteCallbackList还有一个作用,当client进程终止。它会移除client所注冊的全部listener.
    (2)权限验证
    在注冊服务时加入permission权限验证,或是在onTransact方法中进行权限验证;
    (3) AIDL訪问流程总结:
    先创建一个Service和一个AIDL接口。再创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法;在Service的onBind方法中返回这个对象,再在client就能够绑定服务端service。建立连接后就能够訪问远程服务端的方法了。

    6.5 ContentProvider

    和Messenger一样,ContentProvider底层同样是Binder.系统预置了非常多ContentProvider,如通讯录、日程表等。仅仅需通过ContentResolver的query/update/insert/delete就能够跨进程訪问这些信息。详细实现过程例如以下:
    1) 新建一个继承自系统ContentProvider的Provider,并重写onCreate, query, getType, insert, delete, update六个方法。


    这六个进程执行在ContentProvider的进程中,除了onCreate由系统回调执行在主线程中,其它五个方法执行在Binder线程池中。


    2) 注冊Provider

    <provider android:name=".provider.XXProvider"
            android:authorities="com.xx.xx.provider"
            android:permission="com.xx.PROVIDER"
            android:process=":provider">
    </provider>

    3) 在还有一个进程中訪问我们定义的ContentProvider

    Uri uri = Uri.parse("content://com.xx.xx.provider");
    getContentResolver().query(uri, null, null, null, null);
    getContentResolver().query(uri,)

    4) ContentProvider数据源发生变化时。可通过ContentResolver的notifyChange方法来通知外界数据发生改变。外界可通过ContentResolver的registerContentObserver方法来注冊观察者。通过unregisterContentObserver方法来解除观察者。

    6.6 Socket

    Socket分为流式套接字和用户数据报套接字。各自是对应于网络的传输控制层中的TCP/UDP
    TCP:面向连接的协议,稳定的双向通信功能,连接须要“三次握手”。提供了超时重传机制,具有非常高的稳定性;
    UDP:面向无连接协议。不稳定的单向通信功能,也可提供双向通信功能。效率高,不能保证数据一定能正确传输。


    实现步骤:
    1)服务端在新建一个Service,并建立TCP服务

     @Override
        public void onCreate() {
            new Thread(new TcpServer()).start();//堵塞监听client连接
            super.onCreate();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            throw null;
        }
    
        private class TcpServer implements Runnable {
            @Override
            public void run() {
                ServerSocket serverSocket;
                try {
                    //监听8688端口
                    serverSocket = new ServerSocket(8688);
                } catch (IOException e) {
                    return;
                }
                while (!isServiceDestroyed) {
                    try {
                        // 接受client请求。而且堵塞直到接收到消息
                        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 {
            // 用于接收client消息,将client的二进制数据流格式转成文本格式
            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            // 用于向client发送消息
            PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
            out.println("您好。我是服务端");
            while (!isServiceDestroyed) {
                String str = in.readLine();//通过数据流的readLine。可直接变成文本格式
                Log.i("moon", "收到client发来的信息" + str);
                if (TextUtils.isEmpty(str)) {
                    //client断开了连接
                    Log.i("moon", "client断开连接");
                    break;
                }
                String message = "收到了client的信息为:" + str;
                // 从client收到的消息加工再发送给client
                out.println(message);
            }
            out.close();//关闭流
            in.close();
            client.close();
        }

    2)注冊服务

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

    3) client先建立连接服务端socket

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

    4) client和服务端通信。通过while循环不断去读取服务器发送过来的消息

    // 接收服务器端的消息
    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() + "
    " + "服务端:" + msg);
                              }
                          }
            );
        }
    }

    5) client在onCreate中单开线程启动连接服务

    Intent service = new Intent(this, SocketServerService.class);
            startService(service);
            new Thread() {
                @Override
                public void run() {
                    connectSocketServer();
                }
            }.start();
    

    6.7 进程间通信方式比較

    这里写图片描写叙述

  • 相关阅读:
    在线教育项目-day02【讲师分页功能和多条件组合查询功能】
    在线教育项目-day02【统一结果返回】
    Go语言数组和切片的原理
    Go语言单元测试与基准测试
    PHPWord导出word文档
    Go语言反射reflect
    漫画:什么是HTTPS?
    什么是shell和终端?
    Go接口interface
    Go变量逃逸分析
  • 原文地址:https://www.cnblogs.com/wzjhoutai/p/7306386.html
Copyright © 2020-2023  润新知