Android中级——IPC

IPC

  • IPC是什么?
  • 多进程带来的问题
  • IPC前提
    • Serializable
    • Parcelable
    • Binder
  • Android中的IPC
    • Bundle
    • 文件共享
    • Messenger
    • AIDL
    • ContentProvider
    • Socket
    • 不同IPC优缺点
  • Binder连接池

IPC是什么?

Inter-Process Communcation,含义为进程间通信或者跨进程通信

Android中的多进程是指一个应用中存在多个进程的情况,默认进程名为包名,使用多进程需要给四大组件设置android:process属性

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.demo.demo0">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".ThirdActivity"
            android:process="com.demo.demo0.remote" />
        <activity
            android:name=".SecondActivity"
            android:process=":remote" />
        <activity
            android:name=".MainActivity"
            android:process="">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  • 以 : 开头的进程会附加包名,属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程
  • 不以 : 开头的进程为全局进程,其他应用可通过相同的ShareUID及签名和它跑在同一进程
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startActivity(new Intent(this, SecondActivity.class));
    }
}
public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        startActivity(new Intent(this, ThirdActivity.class));
    }
}
public class ThirdActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
    }
}

对于如上的配置,启动3个Activity后,在AS中可看到有三个进程

在这里插入图片描述

多进程带来的问题

新建一个类UserManger

public class UserManager {
    public static int sUserId = 1;
}

在MainActivity中修改sUserId为2并打印

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        UserManager.sUserId = 2;
        Log.d(TAG, "onCreate: sUserId = " + UserManager.sUserId);
        startActivity(new Intent(this, SecondActivity.class));
    }
}

在SecondActivity再次打印

public class SecondActivity extends AppCompatActivity {

    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Log.d(TAG, "onCreate: sUserId = " + UserManager.sUserId);
        startActivity(new Intent(this, ThirdActivity.class));
    }
}

结果却不同

在这里插入图片描述

不同进程的组件会拥有独立的虚拟机、Application和内存空间,会造成

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效
  • SharedPreferences的可靠性下降
  • Application会多次创建

IPC前提

Serializable

对于实现Serializable的类

public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    private int userId;
    private String userName;
    private boolean isMale;

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }
}

可通过ObjectOutputStream和ObjectInputStream实现序列化和反序列化,恢复后的对象内容完全一样但不是同一对象

  • 只有序列化后的数据中的serialVersionUID和当前类的serialVersionUID相同才能正常被反序列化
  • static和tranisient修饰的域不会参与序列化过程
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        User user = new User(0, "tom", true);
        try (ObjectOutputStream out = new ObjectOutputStream(openFileOutput("data", Context.MODE_PRIVATE))) {
            out.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (ObjectInputStream in = new ObjectInputStream(openFileInput("data"))) {
            User newUser = (User) in.readObject();
            Log.d(TAG, "onCreate: user == newUser " + (user == newUser));
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Parcelable

对于实现Parcelable的类可通过Intent和Binder传输

public class User implements Parcelable {

    private int userId;
    private String userName;
    private boolean isMale;

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

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

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(userId);
        out.writeString(userName);
        out.writeInt(isMale ? 1 : 0);
    }


    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {

        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

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

    private User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readInt() == 1;
    }
}

各个方法功能如下

在这里插入图片描述

Binder

以下通过AIDL分析Binder,创建Book.java

public class Book implements Parcelable {

    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {

        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

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

    private Book(Parcel source) {
        bookId = source.readInt();
        bookName = source.readString();
    }
}

创建Book.aidl,声明Book

parcelable Book;

创建IBookManager.aidl,需要手动import

import com.demo.demo0.Book;

interface IBookManager {
   List<Book> getBookList();
   void addBook(in Book book);
}

在AS菜单栏的Build,点击Clean Project,再点击Rebuild Project,会生成IBookManager.java,位置如下图

在这里插入图片描述

public interface IBookManager extends android.os.IInterface {
    /**
     * Default implementation for IBookManager.
     */
    public static class Default implements com.demo.demo0.IBookManager {
        @Override
        public java.util.List<com.demo.demo0.Book> getBookList() throws android.os.RemoteException {
            return null;
        }

        @Override
        public void addBook(com.demo.demo0.Book book) throws android.os.RemoteException {
        }

        @Override
        public android.os.IBinder asBinder() {
            return null;
        }
    }

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.demo.demo0.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.demo.demo0.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.demo.demo0.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.demo.demo0.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.demo.demo0.IBookManager))) {
                return ((com.demo.demo0.IBookManager) iin);
            }
            return new com.demo.demo0.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.demo.demo0.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(descriptor);
                    com.demo.demo0.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.demo.demo0.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.demo.demo0.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.demo.demo0.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.demo.demo0.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getBookList();
                    }
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.demo.demo0.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.demo.demo0.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().addBook(book);
                        return;
                    }
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            public static com.demo.demo0.IBookManager sDefaultImpl;
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

        public static boolean setDefaultImpl(com.demo.demo0.IBookManager impl) {
            // Only one user of this interface can use this function
            // at a time. This is a heuristic to detect if two different
            // users in the same process use this function.
            if (Stub.Proxy.sDefaultImpl != null) {
                throw new IllegalStateException("setDefaultImpl() called twice");
            }
            if (impl != null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }

        public static com.demo.demo0.IBookManager getDefaultImpl() {
            return Stub.Proxy.sDefaultImpl;
        }
    }

    public java.util.List<com.demo.demo0.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.demo.demo0.Book book) throws android.os.RemoteException;
}
  • 所有在Binder中传输的接口都需要继承IInterface
  • DESCRIPTOR:唯一标识,用类名表示
  • asInterface():将服务端的Binder对象转换成客户端所需的AIDL接口对象,同一进程返回Stub,不同进程返回Stub.proxy
  • asBinder():返回当前Binder
  • onTransact():运行在服务端的Binder线程池中,处理跨进程请求,通过code判断目标方法,从data获取方法参数,向reply写入返回值
  • getBookList():客户端调用时,创建输入型Parcel对象_data(并写入参数)、输出型Parcel对象_reply和返回值对象List,接着调用transact()发起Remote Process Call并挂起线程。随后服务端的onTransact()被调用,直到结束后线程继续执行,并从reply获取返回结果
  • addBook():同上

在这里插入图片描述

当Binder所在服务端进程异常终止时,会导致远程调用失败,通过linkToDeath()可为Binder设置一个死亡代理,当Binder死亡时回调binderDied()可再次重新发起请求

IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
    
    }
};
Binder binder = new Binder();
binder.linkToDeath(deathRecipient, 0);

或者在onServiceDisconnected()中重连远程服务,区别在于

  • onServiceDisconnected()在客户端的UI线程中被回调
  • binderDied()在客户端的Binder线程池中被回调,不能访问UI

Android中的IPC

Bundle

Activity、Service、Reciver都支持在Intent中传递Bundle数据,传输的数据必须能够被序列化

文件共享

Android中的文件可以支持并发读写,两个进程可通过读 / 写同一个文件来交换数据,但只适合对数据同步要求不高的进程之间进行通信

SharedPreference通过XML文件来存储键值对,但系统对其读写具有缓存策略,内存中只有一份SharedPreference文件的缓存,在多进程模式下会变得不可靠

Messenger

Messenger底层实现基于AIDL,一次处理一个请求,在服务端不用考虑线程同步

服务端需指定android:process,代码如下

  • 新建ServiceMessengerHandler处理客户端请求,并从replyTo获取clientMessenger返回响应数据
  • 新建ServiceMessenger在onBind()返回其Binder
public class MessengerService extends Service {

    public static final String TAG = "MessengerService";
    private final Messenger mServiceMessenger = new Messenger(new ServiceMessengerHandler());

    private static class ServiceMessengerHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            Log.d(TAG, "handleMessage: " + msg.getData().getString("client"));
            Messenger clientMessenger = msg.replyTo;
            Message serviceMsg = new Message();
            Bundle bundle = new Bundle();
            bundle.putString("service", "msg from service");
            serviceMsg.setData(bundle);
            try {
                clientMessenger.send(serviceMsg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public MessengerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mServiceMessenger.getBinder();
    }
}

如下为客户端的代码

  • 新建ClientMessengerHandler处理服务端返回的数据
  • 新建ClientMessenger传给服务端,用于服务端传递数据
  • 在成功绑定服务时获取serviceMessenger发送数据
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private Messenger mClientMessenger = new Messenger(new ClientMessengerHandler());

    private static class ClientMessengerHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            Log.d(TAG, "handleMessage: " + msg.getData().getString("service"));
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Messenger serviceMessenger = new Messenger(service);
            Message clientMsg = new Message();
            Bundle data = new Bundle();
            data.putString("client", "msg from client");
            clientMsg.setData(data);
            clientMsg.replyTo = mClientMessenger;
            try {
                serviceMessenger.send(clientMsg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

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

运行程序,客户端和服务端都成功接收到数据

在这里插入图片描述

AIDL

Messenger只能串行处理客户端的请求,当需要并行处理客户端请求或需要调用服务端的方法时可以使用AIDL,AIDL支持的数据类型有

  • 基本数据类型,除此之外的参数需标上方向:in、out或inout
  • String和CharSequence
  • ArrayList,其元素都必须被AIDL所支持
  • HashMap,其元素都必须被AIDL所支持
  • 实现了Parcelable的对象,需新建同名aidl文件并进行声明,使用时需import
  • AIDL接口本身可以在AIDL文件中使用,使用时需import

需要注意,AIDL相关文件最好放在同一个包中,且包结构在服务端和客户端要保持一致,否则无法反序列化

创建Book.java,实现Parcelable

public class Book implements Parcelable {

    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {

        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

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

    private Book(Parcel source) {
        bookId = source.readInt();
        bookName = source.readString();
    }
}

创建Book.aidl,声明Book

parcelable Book;

创建IOnNewBookArrivedListener.aidl,用于回调客户端

import com.demo.demo0.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

创建IBookManager.aidl

import com.demo.demo0.Book;
import com.demo.demo0.IOnNewBookArrivedListener;

interface IBookManager {
   List<Book> getBookList();
   void addBook(in Book book);
   void registerListener(IOnNewBookArrivedListener listener);
   void unRegisterListener(IOnNewBookArrivedListener listener);
}

创建服务端的BookManagerService

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            int N = mListenerList.beginBroadcast();
            Log.d(TAG, "registerListener: mListenerList size = " + N);
            mListenerList.finishBroadcast();
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
            int N = mListenerList.beginBroadcast();
            Log.d(TAG, "unRegisterListener: mListenerList size = " + N);
            mListenerList.finishBroadcast();
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.demo.demo0.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 != null && !packageName.startsWith("com.demo")) {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "book1"));
        mBookList.add(new Book(2, "book2"));
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.demo.demo0.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mIsServiceDestroyed.set(true);
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if (listener != null) {
                listener.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }

    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int booId = mBookList.size() + 1;
                Book newBook = new Book(booId, "book" + booId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 使用CopyOnWriteArrayList支持并发读/写,Binder会按照List的规范去访问数据并返回一个ArrayList给客户端
  • 使用RemoteCallbackList存储客户端listener,内部实现了线程同步,客户端终止后还能自动移除,否则Binder会将客户端过来的对象反序化成新的对象,对象不一样无法unregister
  • 可以在onBiner()或onTransact()中校验权限
  • 内部创建ServiceWorker每5秒添加数据并回调客户端

创建客户端MainActivity

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
    private IBookManager mIBookManager;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            Log.d(TAG, "handleMessage: receive new book: " + msg.obj);
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIBookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> bookList = mIBookManager.getBookList();
                Log.d(TAG, "onServiceConnected: bookList type = " + bookList.getClass().getCanonicalName());
                Log.d(TAG, "onServiceConnected: bookList = " + bookList.toString());
                mIBookManager.registerListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mIBookManager = null;
            Log.d(TAG, "onServiceDisconnected: ");
        }
    };

    private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mIBookManager != null && mIBookManager.asBinder().isBinderAlive()) {
            try {
                mIBookManager.unRegisterListener(mIOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
    }
}

manifest文件如下,为BookManagerService设置不同进程,申明权限,和使用权限

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.demo.demo0">

    <permission
        android:name="com.demo.demo0.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal" />

    <uses-permission android:name="com.demo.demo0.permission.ACCESS_BOOK_SERVICE" />

    <application
        android:name=".Config"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

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

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

ContentProvider

可以看Android基础——ContentProvider和contentResolver,不再赘述

Socket

分为流式套接字和用户数据报套接字,分别对应网络传输控制层中的TCP和UDP协议,使用需声明网络权限

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

服务端需指定android:process,代码如下

  • 监听8688端口,每当有客户端连接时,就生成一个Socket
  • 客户端断开连接时,获取的输入流为null,服务端也断开连接
  • 当收到客户端信息后随机回复
public class TCPServerService extends Service {

    private boolean mIsServiceDestroy = false;
    private String[] mDefinedMessages = new String[]{
            "随机回复1",
            "随机回复2",
            "随机回复3",
            "随机回复4",
            "随机回复5",
    };

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

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

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

    private class TcpServer implements Runnable {

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(8688);
            } catch (IOException e) {
                System.err.println("fail to connect 8688");
                e.printStackTrace();
                return;
            }
            while (!mIsServiceDestroy) {
                try {
                    final Socket client = serverSocket.accept();
                    System.out.println("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 {
        try (
                BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
                PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true)
        ) {
            out.println("welcome to chatBox");
            while (!mIsServiceDestroy) {
                String str = in.readLine();
                System.out.println("client send: " + str);
                if (str == null) {
                    System.out.println("client quit");
                    break;
                }
                int i = new Random().nextInt(mDefinedMessages.length);
                String msg = mDefinedMessages[i];
                out.println(msg);
            }
        }
    }
}

客户端,代码如下

  • 创建线程连接服务端Socket,因为不能在主线程中访问网络,若连接失败会再1s后重连
  • 服务端断开连接时,获取的输入流为null,客户端也断开连接
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    private static final int MESSAGE_SOCKET_CONNECTED = 2;
    private static final String TAG = "MainActivity";

    private Button mSendButton;
    private TextView mMessageTextView;
    private EditText mMessageEditText;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case MESSAGE_RECEIVE_NEW_MSG:
                    mMessageTextView.setText(mMessageTextView.getText() + (String) msg.obj);
                    break;
                case MESSAGE_SOCKET_CONNECTED:
                    mSendButton.setEnabled(true);
                    break;
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMessageTextView = findViewById(R.id.msg_container);
        mMessageEditText = findViewById(R.id.msg);
        mSendButton = findViewById(R.id.send);
        mSendButton.setOnClickListener(this);
        Intent service = new Intent(this, TCPServerService.class);
        startService(service);
        new Thread() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.send:
                String msg = mMessageEditText.getText().toString();
                if (TextUtils.isEmpty(msg)) {
                    return;
                }
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        if (mPrintWriter != null) {
                            mPrintWriter.println(msg);
                        }
                    }
                }).start();
                mMessageEditText.setText("");
                String time = formatDateTime(System.currentTimeMillis());
                final String showMsg = "client " + time + ": " + msg + "\n";
                mMessageTextView.setText(mMessageTextView.getText() + showMsg);
                break;
        }

    }

    private String formatDateTime(long time) {
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
    }

    private void connectTCPServer() {
        while (mClientSocket == null) {
            try {
                mClientSocket = new Socket("localhost", 8688);
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mClientSocket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                System.out.println("connect server success");
            } catch (IOException e) {
                SystemClock.sleep(1000);
                System.out.println("connect tcp server failed, retry...");
            }
        }
        try (BufferedReader br = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream()))) {
            while (!isFinishing()) {
                String msg = br.readLine();
                System.out.println("server send: " + msg);
                if (msg == null) {
                    System.out.println("server quit...");
                    break;
                }
                String time = formatDateTime(System.currentTimeMillis());
                String showMsg = "server " + time + ": " + msg + "\n";
                mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showMsg).sendToTarget();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

activity_main.xml代码如下,用于显示信息

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/msg_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/msg_container"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/msg"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="8"
            android:hint="输入你要发送的内容" />

        <Button
            android:id="@+id/send"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:text="发送" />
    </LinearLayout>
</RelativeLayout>

通信过程如下

在这里插入图片描述

不同IPC优缺点

在这里插入图片描述

Binder连接池

AIDL使用过程:

  • 新建一个类继承Stub并实现抽象方法
  • 新建一个Service在onBind()中返回该类的对象
  • 在客户端绑定Service

Service会在后台运行占据内存,不可能为每一个AIDL都新建一个Service,这时候就需要使用Binder连接池将Binder请求统一转发到Service

在这里插入图片描述

创建ISecurityCenter.aidl

interface ISecurityCenter {
    String encrypt(String content);
    String decrypt(String password);
}

创建SecurityCenterImpl.java

public class SecurityCenterImpl extends ISecurityCenter.Stub {

    private static final char SECRET_CODE = '^';

    @Override
    public String encrypt(String content) throws RemoteException {
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] ^= SECRET_CODE;
        }
        return new String(chars);
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}

创建ICompute.aidl

interface ICompute {
   int add(int a, int b);
}

创建ComputeImpl.java

public class ComputeImpl extends ICompute.Stub {
    @Override
    public int add(int a, int b) throws RemoteException {
        return a + b;
    }
}

创建IBinderSelector.aidl

interface IBinderSelector {
    IBinder queryBinder(int binderCode);
}

创建BinderSelectorImpl.java

  • 利用不同的binderCode返回不同的Binder
  • 当要添加新业务时,只需要新增.aidl文件和修改BinderSelectorImpl
public class BinderSelectorImpl extends IBinderSelector.Stub {

    public static final int BINDER_COMPUTE = 1;
    public static final int BINDER_SECURITY_CENTER = 2;

    public BinderSelectorImpl() {
    }

    @Override
    public IBinder queryBinder(int binderCode) throws RemoteException {
        IBinder binder = null;
        switch (binderCode) {
            case BINDER_SECURITY_CENTER:
                binder = new SecurityCenterImpl();
                break;
            case BINDER_COMPUTE:
                binder = new ComputeImpl();
                break;
        }
        return binder;
    }
}

创建BinderPoolService.java,返回BinderSelectorImpl,但并不是给客户端使用,而是给BinderPool绑定

public class BinderPoolService extends Service {

    private Binder mBinderPool = new BinderSelectorImpl();

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

创建BinderPool.java

  • 使用单例,初始化时绑定BinderPoolService,并实现断线重连
  • 通过CountDownLatch将异步的bindService转为同步,确保初始化时成功连上Service,才能调用queryBinder()
public class BinderPool {

    private static final String TAG = "BinderPool";

    private Context mContext;
    private IBinderSelector mBinderSelector;
    private static volatile BinderPool sInstance;
    private CountDownLatch mCountDownLatch;

    private BinderPool(Context context) {
        mContext = context;
        connectBinderPoolService();
    }

    public static BinderPool getInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {
        mCountDownLatch = new CountDownLatch(1);
        Intent service = new Intent(mContext, BinderPoolService.class);
        mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        if (mBinderSelector != null) {
            try {
                binder = mBinderSelector.queryBinder(binderCode);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return binder;
    }

    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderSelector = IBinderSelector.Stub.asInterface(service);
            try {
                mBinderSelector.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

        private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                Log.d(TAG, "binderDied: ");
                mBinderSelector.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
                mBinderSelector = null;
                connectBinderPoolService();
            }
        };
    };
}

MainActivity代码如下

  • 使用时需利用线程,因为连接Service或调用Binder方式可能都是耗时的
  • 根据code获取Binder,转为对应的实现即可调用
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private ISecurityCenter mSecurityCenter;
    private ICompute mCompute;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                work();
            }
        }).start();
    }

    public void work() {
        BinderPool binderPool = BinderPool.getInstance(this);
        IBinder securityBinder = binderPool.queryBinder(BinderSelectorImpl.BINDER_SECURITY_CENTER);
        mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);
        String msg = "hello";
        try {
            String encryptMsg = mSecurityCenter.encrypt(msg);
            Log.d(TAG, "onCreate: encrypt = " + encryptMsg);
            Log.d(TAG, "onCreate: decrypt = " + mSecurityCenter.decrypt(encryptMsg));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        IBinder computeBinder = binderPool.queryBinder(BinderSelectorImpl.BINDER_COMPUTE);
        mCompute = ComputeImpl.asInterface(computeBinder);
        try {
            Log.d(TAG, "onCreate: add = " + mCompute.add(1, 2));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/34889.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【FFMPEG】AVFilter使用流程

流程图 核心类 AVFilterGraph ⽤于统合这整个滤波过程的结构体 AVFilter 滤波器&#xff0c;滤波器的实现是通过AVFilter以及位于其下的结构体/函数来维护的 AVFilterContext ⼀个滤波器实例&#xff0c;即使是同⼀个滤波器&#xff0c;但是在进⾏实际的滤波时&#xff0c;也…

易模为真人3D手办制作带来了创新

3d打印技术是一项近年来迅速发展的先进制造技术&#xff0c;逐渐在各个领域展现出无限的潜力。其中&#xff0c;3d打印真人手办成为了一个备受关注的领域。在市面上&#xff0c;我们常常可以看到一些热门动漫角色或明星的真人3d手办&#xff0c;逼真的细节和完美的再现度让人们…

实验室仪器管理系统/基于微信小程序的实验室仪器管理系统

摘 要 随着当今网络的发展&#xff0c;时代的进步&#xff0c;各行各业也在发生着变化&#xff0c;于是网络已经逐步进入人们的生活&#xff0c;给我们生活或者工作提供了新的方向新的可能。 本毕业设计的内容是设计实现一个实验室仪器管理系统。使用微信开发者是以java语言…

【机器学习】主成分分析实现案例 (PCA)

一、说明 这篇文章的目的是提供主成分分析&#xff08;PCA&#xff09;的完整和简化的解释。我们将逐步介绍它是如何工作的&#xff0c;这样每个人都可以理解并使用它&#xff0c;即使是那些没有强大数学背景的人。 PCA是网络上广泛覆盖的机器学习方法&#xff0c;并且有一些关…

3.Hive SQL数据定义语言(DDL)

1. 数据定义语言概述 1.1 常见的开发方式 &#xff08;1&#xff09; Hive CLI、Beeline CLI Hive自带的命令行客户端 优点&#xff1a;不需要额外安装 缺点&#xff1a;编写SQL环境恶劣&#xff0c;无有效提示&#xff0c;无语法高亮&#xff0c;误操作率高 &#xff08;2&…

LangChain大型语言模型(LLM)应用开发(一):Models, Prompts and Output Parsers

LangChain是一个基于大语言模型&#xff08;如ChatGPT&#xff09;用于构建端到端语言模型应用的 Python 框架。它提供了一套工具、组件和接口&#xff0c;可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互&#x…

ipad手写笔一定要买苹果的吗?苹果平板平替电容笔排行

苹果原装Pencil&#xff0c;无疑是一个性能很出色的电容笔&#xff0c;但是&#xff0c;由于其的价格也很高&#xff0c;如果丢失了&#xff0c;或者弄坏&#xff0c;那就太可惜了。而且购买如此昂贵的苹果原装电容笔&#xff0c;仅仅只用于书写笔记方面&#xff0c;显得有点浪…

【开发问题】sqlserver怎么开启cdc

怎么开启 执行sql1、创建cdc​2.如上执行完毕之后&#xff0c;会在<database_name>数据库下的“系统表”中创建如下六个系统表&#xff1a;3.验证SQLServer库级别CDC是否启用4.启用SQLServer表级别CDC功能&#xff08;针对某一张表&#xff09;5、验证SQLServer表级别是否…

本地部署开源大模型的完整教程:LangChain + Streamlit+ Llama

在过去的几个月里&#xff0c;大型语言模型(llm)获得了极大的关注&#xff0c;这些模型创造了令人兴奋的前景&#xff0c;特别是对于从事聊天机器人、个人助理和内容创作的开发人员。 大型语言模型(llm)是指能够生成与人类语言非常相似的文本并以自然方式理解提示的机器学习模型…

【STM32智能车】小车状态

【STM32智能车】小车状态 搭建智能车 65MM轮径小车所选材料安装说明直行测试智能车可能存在的状态 智能车功能丰富&#xff0c;我们从最基础的开始&#xff0c;先来搭建一个智能车吧~。 搭建智能车 我们之前用了一个测试板子去学习调试电机&#xff0c;是时候拼装一个简单的车来…

Leetcode-每日一题【92.反转链表Ⅱ】

题目 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4输出&#xff1a;…

详细解释lvs的工作原理

vsl用于集群中的直接路由它的原理如下 如果在公司并发太高了怎么解决 1.加配置cpu 内存 带宽 ssd高效硬盘 2.加服务器 为用户提供服务 横向扩展 集群是什么 由的多台主机构成,相当于一台大型计算机,只提供一个访问入口(域名与ip地址) 集群用在那个场景 高并发场景 vrrp是…

服务无法注册进Eureka

相同的配置&#xff0c;在demo里能注册&#xff0c;在自己项目的无法注册&#xff0c;眼睛都快盯出老花眼了&#xff0c;还是不行&#xff0c;果然出现的问题只有在发现问题以后才觉得简单&#xff08;虽然确实是小问题&#xff0c;但是排查了一整天&#xff0c;值得记录一下&a…

论文阅读:Segment Anything之阅读笔记

目录 引言整体结构介绍论文问答代码仓库中&#xff0c;模型哪部分转换为了ONNX格式&#xff1f;以及如何转的&#xff1f;Mask decoder部分 Transformer decoder block?如何整合image_embedding&#xff0c;image_pe, sparse_prompt_embedding和dense_prompt_embedding的&…

ue4:Dota总结—HUD篇

1.绘制ui&#xff1a; DrawMoney&#xff1a; DrawPower&#xff1a; 点击ui响应事件&#xff1a; 点击响应显示对应的模型&#xff1a; 点击ui拖动模型跟随鼠标移动&#xff1a; 显示ui&#xff1a;PlayerContrler&#xff1a;

内网IP怎么用域名让外网访问,域名动态解析和静态区别?

域名解析是将域名与公网IP进行对应关系&#xff0c;实现访问域名即访问到对应IP应用的方式。域名解析分静态域名解析和动态域名解析的区别&#xff0c;它们的区别在哪&#xff1f;内网IP服务器怎么用域名让外网连接访问&#xff1f;这些都是需要我们有所了解掌握的。 这里不但…

如何基于GeoToolKit/INT实现矢量流线的聚集动画效果示例

继续在上一篇文章的基础上&#xff0c;利用相同的数据处理方法统一了不同年代地层的数据格式&#xff08;目前js解析支持的格式有ZMap、TS、XYZ和XYZA等&#xff09;&#xff0c;本文主要基于GeoToolKit/INT组件&#xff0c;针对地质研究经常在二维等值线基础上模拟计算地层中物…

Quiz 14_2-2: Using Web Services | Python for Everybody 配套练习_解题记录

文章目录 Python for Everybody课程简介Quiz 14_2-2: Using Web Services单选题&#xff08;1-15&#xff09;操作题Autograder 1: Extract Data from JSONAutograder 2: Calling a JSON API Python for Everybody 课程简介 Python for Everybody 零基础程序设计&#xff08;P…

NSS [NSSCTF 2022 Spring Recruit]ezgame

NSS [NSSCTF 2022 Spring Recruit]ezgame 前端小游戏&#xff0c;乐。

Spring源码整体脉络介绍及源码编译

需完成的任务 类------------------------------------------BeanFactory----------------------------------------->Bean【BeanFactory调用getBean()生产出来的】 BeanFactory Spring顶层核心接口&#xff0c;使用了简单工厂模式【根据名字&#xff0c;生产出不同的Bean…
最新文章