1. Introduction to IPC

IPC is short for Inter-process Communication. It refers to the Process of data exchange between two processes.

Typically, an application has only one process on Android. In the simplest case, a process can have only one thread (of course, that’s not always possible), called the main thread, also known as the UI thread.

Sometimes an application needs to adopt the multi-process mode for some reasons. In this case, the IPC mechanism is required to communicate between different processes in the application. Or if two different applications need to exchange data, they also need to rely on the IPC scheme provided by the Android system

IPC is not unique to Android; every operating system has its own IPC mechanism. Interprocess communication is carried out on Windows via clipboard, pipeline, oil tank, etc. Linux uses namespaces, shared content, semaphores, and so on to communicate between processes.

2. Enable multiple processes

The same application can enable multi-process mode by assigning the Android: Process attribute to the four components in AndroidMenifest.

A private process whose name starts with: belongs to the current application. Components of other applications cannot run in the same process.

A process whose name does not start with: belongs to a global process. Other applications can run in the same process with this process by using ShareUID. Two applications can run in the same process using ShareUID and have the same signature. They can share data directories, component information, and memory data.

Begin with:

<service
    android:name=".MyService"
    android:process=":remote" />
Copy the code

Do not start with:

<service
    android:name=".MyService"
    android:process="com.freeman.remote" />
Copy the code

3. Multi-process impact

  1. Static members and singletons are completely invalidated.

  2. The thread synchronization mechanism has completely failed.

  3. The reliability of SharedPreferences decreases

  4. Application will be created multiple times

  • Problems 1 and 2 are caused by different processes and different memory blocks.

  • Problem 3 Because SharedPreferences do not support read and write operations by two processes, data may be lost.

  • Problem 4 is that when a component runs in a new process, the system creates a new process for it and assigns independent virtual machines. All this process is actually the process of starting an Application. Therefore, the system restarts the Application and creates a new Application.

To solve the problem caused by multiple processes, the system provides many cross-process communication methods, such as:

  1. Intent
  2. File sharing
  3. SharedPreferences
  4. Messenger
  5. AIDL
  6. ContentProvider
  7. Socket

4. IPC serialization

The purpose of cross-process communication is to exchange data, but not all data types can be passed. In addition to basic data types, data types must be serialized or deserialized, that is, data types that implement the Serializable interface or Parcelable interface

4.1, the Serializable

Serializable is a serialization interface (empty interface) provided by Java that provides standard serialization and deserialization operations for objects. Serialization requires only a class that implements the Serializable interface and declares a serialVersionUID.

In addition, to aid the system in the serialization and deserialization of objects, you can declare a long data serivalVersionUID

private static final long serivalVersionUID = 123456578689L;
Copy the code

Serialization saves the object’s information along with the serivalVersionUID in some medium (such as a file or memory). When deserializing, the serivalVersionUID in the medium is compared with the serivalVersionUID declared in the class. If they are the same, the serialized class is the same version as the current class, and the serialization succeeds. If the two are not equal, the current version of the class has changed (perhaps by adding or deleting a method), which will cause serialization to fail.

If the serivalVersionUID is not manually declared, the compiler will automatically generate the serivalVersionUID based on the structure of the current class, so that the deserialization can succeed only if the structure of the class is exactly the same.

In order to deserialize successfully without structural changes to the structure of the class, a fixed value is usually manually specified for serivalVersionUID. This allows maximum data recovery even when a class adds or deletes a variable or method body. Of course, the structure of the class cannot change too much, otherwise deserialization will still fail.

Static member variables are classes, not objects, so they do not participate in the serialization process, nor do member variables marked with transient keywords.

You can override the writeObject and readObject methods to change the system’s default serialization process.

4.2, Parcelable

The Parcelable interface is a unique serialization method in Android. It is more efficient and less memory intensive than Serializable, but it is a bit more cumbersome to use. The Parcelable interface is also recommended for serialization. Bundles, intents, and bitmaps all implement Parcelable.

Implementing the Parcelable interface requires the implementation of four methods for serialization, deserialization, and content description. The Android Parcelable Code Generator is a plugin for Android Studio that does this automatically instead of manually implementing the Parcelable interface.

An example implementation of Parcelable is as follows:


import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {
    private int id;
    private String name;
    private float price;

    public Person() {
    }
    
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    @Override
    public int describeContents() {
        return0; @override public void writeToParcel(Parcel dest, int flags) {dest.writeInt(this.id); dest.writeString(this.name); dest.writeFloat(this.price); Public static final parcelable. Creator<Person> Creator = new Parcelable.Creator<Person>() { @Override public Person createFromParcel(Parcelsource) {
            return new Person(source);
        }

        @Override
        public Person[] newArray(int size) {
            returnnew Person[size]; }}; // Read data to restore protected Person(Parcelin) { this.id = in.readInt(); this.name = in.readString(); this.price = in.readFloat(); }}Copy the code

To implement a Parcelable interface, you need to implement the following methods:

  1. Constructor: Creates the original object from the serialized object
  2. DescribeContents: Specifies the description of an interface. 0 is returned by default
  3. WriteToParcel: Serialization method that writes class data to a parcel container
  4. Static parcelable.Creator interface, which contains two methods
    • CreateFormParcel: Method of deserialization that restores a Parcel to a Java object
    • NewArray: Provides external classes to deserialize this array.

From the above we can see that parcels are written and read in the same order. If the element is a list, you need to pass in a new ArrayList; otherwise, a null pointer exception will be reported. As follows:

list = new ArrayList<String>();
in.readStringList(list);
Copy the code

5. IPC

5.1. Use Intent

  1. Activities, services, and BroadcastReceivers all support transmission of Bundle data in intEnts. Bundles implement the Parcelable interface for transmission between different processes.

  2. An Activity, Service, or BroadcastReceiver that starts another process in one process can attach data to a Bundle and send it as an Intent.

5.2. Use file Sharing

  1. On Windows, a file with an exclusion lock cannot be accessed by other threads, including read and write. Android, on the other hand, is based on Linux, which allows concurrent file reads to proceed without limitation and even allows two threads to read and write to a file at the same time, although this can be problematic.
  2. You can serialize an object to the file system in one process and deserialize it in another (note: it’s not the same object, just the same content).
  3. SharedPreferences is a special case, the system has a certain cache policy for its read/write, that is, there will be a cache of ShardPreferences file in memory, the system to read/write it becomes unreliable, when facing high concurrency read/write access, SharedPreferences has a lot of high probability of data loss. Therefore, IPC does not recommend the adoption of SharedPreferences.

5.3. Use Messenger

Messenger is a lightweight IPC solution. Its underlying implementation is AIDL, which can pass Message objects in different processes. It only handles one request at a time.

  • Server process: The server creates a Service to handle client requests, instantiates a Messenger object through a Handler object, and returns the Messenger object’s underlying Binder in the Service’s onBind.
public class MessengerService extends Service {

    private static final String TAG = MessengerService.class.getSimpleName();

    private class MessengerHandler extends Handler {

        /**
         * @param msg
         */
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case Constants.MSG_FROM_CLIENT:
                    Log.d(TAG, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]");
                    Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
                    Messenger client = msg.replyTo;
                    Message replyMsg = Message.obtain(null, Constants.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString(Constants.MSG_KEY, "I have received your message and will reply to you later!");
                    replyMsg.setData(bundle);
                    try {
                        client.send(replyMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private Messenger mMessenger = new Messenger(new MessengerHandler());


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        returnmMessenger.getBinder(); }}Copy the code
  • Client process: first bind the server Service, and then create a Messenger using the IBinder object of the server. Through this Messenger, you can send messages to the server. The Message type is Message. If a server response is required, a Handler needs to be created and used to create a Messenger (just like the server), which is passed to the server via the replyTo parameter of Message. The server responds to the client using the replyTo parameter of the Message.
public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
    private Messenger mService;

    private class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Constants.MSG_FROM_SERVICE:
                    Log.d(TAG, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]");
                    Toast.makeText(MainActivity.this, "received msg form service: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }



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

    }

    public void bindService(View v) {
        Intent mIntent = new Intent(this, MessengerService.class);
        bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    public void sendMessage(View v) {
        Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
        Bundle data = new Bundle();
        data.putString(Constants.MSG_KEY, "Hello! This is client.");
        msg.setData(data);
        msg.replyTo = mGetReplyMessenger;
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

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

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString(Constants.MSG_KEY, "Hello! This is client.");
            msg.setData(data);
            msg.replyTo = mGetReplyMessenger;
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

 
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
}

Copy the code

Note: The client and server send messages by taking each other’s Messenger. The client uses bindService onServiceConnected, and the server uses message.replyto to retrieve each other’s Messenger.

There is a Hanlder in Messenger that processes messages in the queue in a serial manner. There is no concurrent execution, so we don’t have to worry about thread synchronization.

5.4. Use AIDL

5.4.1 What is AIDL

  • AIDL stands for Android Interface Definition Language. AIDL is a description Language used to define the communication Interface between the server and the client, which can be used to generate code for IPC. AIDL is actually a template in a sense, because it’s not an AIDL file that actually works, it’s an instance of IInterface code that’s generated from it, and AIDL is a template that’s created to keep us from writing code again and again
  • AIDL files have the.aidl suffix
  • If a large number of messages are sent to the server at the same time, the server can only process them one by one. Therefore, a large number of concurrent requests are not suitable for Messenger, and Messenger is only suitable for delivering messages, not calling server-side methods across processes. AIDL solves the problem of concurrent and cross-process method calls, and Messenger is essentially AIDL, but packaged to make it easier for higher level calls.

5.4.2 Function of AIDL

  • For communication between different processes, in The Android system, each process runs in a separate piece of memory, in which it completes its activities, separated from other processes. But sometimes we have a need to interact with each other, to compare and transfer data or delegate tasks, and AIDL was created to meet this need. With AIDL, you can retrieve data from one process and invoke exposed methods from another process to meet the needs of interprocess communication.

  • Generally, the application that exposes methods to call other applications is called the server, and the application that calls methods of other applications is called the client. The client interacts by binding the Service of the server

5.4.3 Data types supported by AIDL files

  1. Basic data types; (Except short)
  2. String and CharSequence;
  3. ArrayList, whose elements must be supported by AIDL;
  4. HashMap, whose elements must be supported by AIDL;
  5. Parcelable, the object that implements the Parcelable interface; Note: If a custom Parcelable object is used in an AIDL file, you must create a new AIDL file with the same name.
  6. AIDL, the AIDL interface itself can also be used in AIDL files.

5.4.4 Server and Client

The service side

Note: The server is the process you want to connect to. The server creates a Service to listen for connection requests from the client, then creates an AIDL file to declare the interface exposed to the client, and finally implements the AIDL interface in the Service.

The client

Bind the Service on the server. Bind the Binder object returned by the server to the type of the AIDL interface. Then you can call the AIDL methods. The client invokes the method of the remote service, and the called method runs in the Binder thread pool of the server. At the same time, the client thread is suspended. If the server method execution is time-consuming, the client thread will block for a long time, resulting in ANR. The onServiceConnected and onServiceDisconnected methods on the client side are both in the UI thread.

5.4.5 Set the permission of AIDL, which can only be invoked by permission

  • Use Permission validation, declared in the manifest, to add Permission validation
<permission android:name="com.freeman.ipc.ACCESS_BOOK_SERVICE"
    android:protectionLevel="normal"/>
Copy the code
  • Verify permissions in the onBinder method on the server side
Public IBinder onBind (Intent Intent) {/ / Permission authentication int check = checkCallingOrSelfPermission ("com.freeman.ipc.ACCESS_BOOK_SERVICE");
    if (check == PackageManager.PERMISSION_DENIED) {
        return null;
    }
    return mBinder;
}
Copy the code

5.4.6 AIDL do not perform time-consuming operations

  • When a client invokes a remote server’s method, the invoked method is run in the server’s Binder thread pool, and the client thread is suspended. If the server method execution is time-consuming, the client thread will be blocked.
  • For example, if the thread is put to sleep for five seconds, when the button is clicked, it is obvious that the button has a feedback effect of being “stuck” because the UI thread is blocked, which can cause ANR. So if you determine that remote methods are time consuming, avoid calling remote methods in the UI thread.
  • The client’sServiceConnectionThe object’sonServiceConnectedonServiceDisconnectedBoth run in UI threads, so they can’t be used to call time-consuming remote methods.
  • Because server-side methods themselves run in the server’s Binder thread pool, server-side methods themselves can be used to execute time-consuming methods without having to open threads in server-side methods to perform asynchronous tasks.

5.4.7 The client initiates communication access in the child thread

When a client initiates a remote request, the client suspends and waits until the server finishes processing and returns data, so remote communication is time consuming and access cannot be initiated in a child thread. Because the Binder method on the server runs in the Binder thread pool, it should be implemented synchronously because it is already running in a thread.

5.4.8 Under what circumstances can remote invocation fail

Binder died accidentally. If the server process terminates abnormally for some reason, the remote call will fail, and if we don’t know that the Binder connection has broken, the client will be affected. With linkToDeath, we can set up a death proxy for Binder, and we’ll be notified when Binder dies.

// Set the death agent messagecenter.asbinder ().linktoDeath (deathRecipient, 0) in the onServiceConnected method of the anonymous class that created ServiceConnection. DeathRecipient DeathRecipient = new IBinder.DeathRecipient() {

    @Override
    public void binderDied() {
        if(messageCenter == null){
            return; } messageCenter.asBinder().unlinkToDeath(deathRecipient, 0); messageCenter = null; AttemptToBindService (); }};Copy the code

5.4.9,Case detail code:

// Book.aidl
package com.freeman.ipc.aidl;

parcelable Book;
Copy the code
// IBookManager.aidl package com.freeman.ipc.aidl; import com.freeman.ipc.aidl.Book; import com.freeman.ipc.aidl.INewBookArrivedListener; IBookManager {List<Book> getBookList(); // All data types in AIDL must be directional,in,out 或者 inout
    // in// out indicates output parameters. // inout indicates input and output parameters. Void addBook(in Book book);

    void registerListener(INewBookArrivedListener listener);
    void unregisterListener(INewBookArrivedListener listener);

}
Copy the code
// INewBookArrivedListener.aidl package com.freeman.ipc.aidl; import com.freeman.ipc.aidl.Book; Interface INewBookArrivedListener {void onNewBookArrived(in Book newBook);
}
Copy the code
public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = BookManagerActivity.class.getSimpleName();
    private static final int MSG_NEW_BOOK_ARRIVED = 0x10;
    private Button getBookListBtn,addBookBtn;
    private TextView displayTextView;
    private IBookManager bookManager;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "handleMessage: new book arrived " + msg.obj);
                    Toast.makeText(BookManagerActivity.this, "new book arrived " + msg.obj, Toast.LENGTH_SHORT).show();
                    break; default: super.handleMessage(msg); }}}; private ServiceConnection mServiceConn = newServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                bookManager.registerListener(listener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private INewBookArrivedListener listener = new INewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, newBook).sendToTarget(); }}; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.book_manager);
        displayTextView = (TextView) findViewById(R.id.displayTextView);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mServiceConn, BIND_AUTO_CREATE);

    }


    public void getBookList(View view) {
        try {
            List<Book> list = bookManager.getBookList();
            Log.d(TAG, "getBookList: " + list.toString());
            displayTextView.setText(list.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    public void addBook(View view) {
        try {
            bookManager.addBook(new Book(3, "Heavenly Dragon eight Bu"));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        if(bookManager ! = null && bookManager.asBinder().isBinderAlive()) { Log.d(TAG,"unregister listener "+ listener); try { bookManager.unregisterListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mServiceConn); super.onDestroy(); }}Copy the code
public class BookManagerService extends Service { private static final String TAG = BookManagerService.class.getSimpleName(); // CopyOnWriteArrayList supports concurrent reading and writing, realizing automatic thread synchronization. Private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); Binder RemoteCallbackList RemoteCallbackList RemoteCallbackList RemoteCallbackList RemoteCallbackList RemoteCallbackList RemoteCallbackList RemoteCallbackList //RemoteCallbackList automatically overflows the listener registered with the client when the client process terminates, and automatically implements thread synchronization internally. private RemoteCallbackList<INewBookArrivedListener> mListeners = new RemoteCallbackList<>(); private AtomicBoolean isServiceDestroied = new AtomicBoolean(false);


    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.d(TAG, "addBook: "+ book.toString()); mBookList.add(book); } @Override public void registerListener(INewBookArrivedListener listener) throws RemoteException { mListeners.register(listener); } @Override public void unregisterListener(INewBookArrivedListener listener) throws RemoteException { mListeners.unregister(listener); }}; @Override public voidonCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Legend of the Condor Heroes"));
        mBookList.add(new Book(2, "Lean on the Sky and Slay the Dragon."));
        new Thread(new ServiceWorker()).start();
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);

        int count = mListeners.beginBroadcast();

        for (int i = 0; i < count; i++) {
            INewBookArrivedListener listener = mListeners.getBroadcastItem(i);
            if(listener ! = null) { listener.onNewBookArrived(book); } } mListeners.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public voidrun() {
            while(! isServiceDestroied.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() +1; Book newBook = new Book(bookId,"new book # "+ bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); }}}} @nullable @override public IBinder onBind(Intent Intent) {// Permission Int check = checkCallingOrSelfPermission("com.freeman.ipc.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }

        return mBinder;
    }

    @Override
    public void onDestroy() {
        isServiceDestroied.set(true); super.onDestroy(); }}Copy the code

5.5. Use ContentProvider

  • Binder and AIDL are the same underlying implementations for sharing data between different applications. Binder and AIDL encapsulates the system and makes it easy to use.
  • The system presets many content Providers, such as address book and schedule, which need to be accessed across processes. Use methods: Inherit the ContentProvider class to implement six abstract methods that run in the ContentProvider process. The other five methods run in the Binder thread pool, except onCreate, which runs in the main thread. The underlying data of the ContentProvider can be an SQLite database, a file, or in-memory data.
  • See code for details:
public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";
    public static final String AUTHORITY = "com.freeman.ipc.Book.Provider";

    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");

    public static final int BOOK_URI_CODE = 0;
    public static final int USER_URI_CODE = 1;
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
        sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
    }

    private Context mContext;
    private SQLiteDatabase mDB;

    @Override
    public boolean onCreate() {
        mContext = getContext();
        initProviderData();

        return true;
    }

    private void initProviderData() {// It is not recommended to perform time-consuming operations in the UI thread mDB = new DBOpenHelper(mContext).getwritableDatabase (); mDB.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME);
        mDB.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME);
        mDB.execSQL("insert into book values(3,'Android');");
        mDB.execSQL("insert into book values(4,'iOS');");
        mDB.execSQL("insert into book values(5,'Kotlin');");
        mDB.execSQL("insert into user values(1,'Flutter',1);");
        mDB.execSQL("insert into user values(2,'Linux',0);");

    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.d(TAG, "query, current thread"+ Thread.currentThread());
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }

        return mDB.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        Log.d(TAG, "getType");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.d(TAG, "insert");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI"+ uri); } mDB.insert(table, null, values); / / notice outside ContentProvider. Change the data in the mContext getContentResolver () notifyChange (uri, null);return uri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.d(TAG, "delete");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }
        int count = mDB.delete(table, selection, selectionArgs);
        if (count > 0) {
            mContext.getContentResolver().notifyChange(uri, null);
        }

        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.d(TAG, "update");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }
        int row = mDB.update(table, values, selection, selectionArgs);
        if (row > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return row;
    }

    private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_URI_CODE:
                tableName = DBOpenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DBOpenHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }

        returntableName; }}Copy the code
public class DBOpenHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "book_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

    private static final int DB_VERSION = 1;

    private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
            + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";

    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
            + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT,"
            + "sex INT)";



    public DBOpenHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);

    }

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

    }
}
Copy the code
public class ProviderActivity extends AppCompatActivity {
    private static final String TAG = ProviderActivity.class.getSimpleName();
    private TextView displayTextView;
    private Handler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        displayTextView = (TextView) findViewById(R.id.displayTextView);
        mHandler = new Handler();

        getContentResolver().registerContentObserver(BookProvider.BOOK_CONTENT_URI, true, new ContentObserver(mHandler) {
            @Override
            public boolean deliverSelfNotifications() {
                returnsuper.deliverSelfNotifications(); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); } @Override public void onChange(boolean selfChange, Uri uri) { Toast.makeText(ProviderActivity.this, uri.toString(), Toast.LENGTH_SHORT).show(); super.onChange(selfChange, uri); }}); } public void insert(View v) { ContentValues values = new ContentValues(); values.put("_id", 1123); values.put("name".Romance of The Three Kingdoms);
        getContentResolver().insert(BookProvider.BOOK_CONTENT_URI, values);

    }
    public void delete(View v) {
        getContentResolver().delete(BookProvider.BOOK_CONTENT_URI, "_id = 4", null);


    }
    public void update(View v) {
        ContentValues values = new ContentValues();
        values.put("_id", 1123); values.put("name"."The New Romance of The Three Kingdoms");
        getContentResolver().update(BookProvider.BOOK_CONTENT_URI, values , "_id = 1123", null);


    }
    public void query(View v) {
        Cursor bookCursor = getContentResolver().query(BookProvider.BOOK_CONTENT_URI, new String[]{"_id"."name"}, null, null, null);
        StringBuilder sb = new StringBuilder();
        while (bookCursor.moveToNext()) {
            Book book = new Book(bookCursor.getInt(0),bookCursor.getString(1));
            sb.append(book.toString()).append("\n");
        }
        sb.append("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --").append("\n");
        bookCursor.close();

        Cursor userCursor = getContentResolver().query(BookProvider.USER_CONTENT_URI, new String[]{"_id"."name"."sex"}, null, null, null);
        while (userCursor.moveToNext()) {
            sb.append(userCursor.getInt(0))
                    .append(userCursor.getString(1)).append(",")
                    .append(userCursor.getInt(2)).append(",")
                    .append("\n");
        }
        sb.append("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); userCursor.close(); displayTextView.setText(sb.toString()); }}Copy the code

5.6. Use sockets

  • Socket originated from Unix, and one of the basic philosophies of Unix is that “everything is a file”, which can be operated in “open – read – write/read – close” mode. Socket is an implementation of this mode. Socket data transmission on the network is a special I/O, and Socket is also a file descriptor. Sockets also have a file-like function call: Socket(), which returns an integer Socket descriptor, through which subsequent connections are established, data transfers, and other operations are performed.

  • There are two common Socket types: streaming Socket (SOCK_STREAM) and datagram Socket (SOCK_DGRAM). Streaming is a connection-oriented Socket for connection-oriented TCP service applications. A datagram Socket is a connectionless Socket corresponding to a connectionless UDP service application.

The Socket itself can transmit any byte stream. When it comes to sockets, we must talk about TCP/IP five-tier network model:

  • Application layer: specify the data format of the application program, the main protocol HTTP, FTP, WebSocket, POP3, etc.
  • Transport layer: establish “port to port” communication, the main protocol: TCP, UDP;
  • Network layer: establish “host to host” communication, the main protocol: IP, ARP, THE main role of IP protocol: one is to assign IP address for each computer, the other is to determine which address in the same subnet;
  • Data link layer: determine the grouping mode of electrical signals, the main protocol: Ethernet protocol;
  • Physical layer: responsible for the transmission of electrical signals.

Socket is an interface (API) that connects the application layer to the transport layer.

5.6.1 Client code:

public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = "TCPClientActivity";
    public static final int MSG_RECEIVED = 0x10;
    public static final int MSG_READY = 0x11;
    private EditText editText;
    private TextView textView;
    private PrintWriter mPrintWriter;
    private Socket mClientSocket;
    private Button sendBtn;
    private StringBuilder stringBuilder;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_READY:
                    sendBtn.setEnabled(true);
                    break;
                case MSG_RECEIVED:
                    stringBuilder.append(msg.obj).append("\n");
                    textView.setText(stringBuilder.toString());
                    break; default: super.handleMessage(msg); }}}; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.tcp_client_activity);
        editText = (EditText) findViewById(R.id.editText);
        textView = (TextView) findViewById(R.id.displayTextView);
        sendBtn = (Button) findViewById(R.id.sendBtn);
        sendBtn.setOnClickListener(this);
        sendBtn.setEnabled(false);
        stringBuilder = new StringBuilder();

        Intent intent = new Intent(TCPClientActivity.this, TCPServerService.class);
        startService(intent);

        new Thread(){
            @Override
            public void run() {
                connectTcpServer();
            }
        }.start();
    }


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

    private void connectTcpServer() {
        Socket socket = null;
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8888);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())
                ), true);
                mHandler.sendEmptyMessage(MSG_READY);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // receive message
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(! isFinishing()) { try { String msg = bufferedReader.readLine();if(msg ! = null) { String time = formatDateTime(System.currentTimeMillis()); String showedMsg ="server " + time + ":" + msg
                            + "\n";
                    mHandler.obtainMessage(MSG_RECEIVED, showedMsg).sendToTarget();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    @Override
    public void onClick(View v) {
        if(mPrintWriter ! = null) { String msg = editText.getText().toString(); mPrintWriter.println(msg); editText.setText("");
            String time = formatDateTime(System.currentTimeMillis());
            String showedMsg = "self " + time + ":" + msg + "\n";
            stringBuilder.append(showedMsg);

        }

    }

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

5.6.2 Server-side code

public class TCPServerService extends Service {
    private static final String TAG = "TCPServerService";
    private boolean isServiceDestroyed = false;
    private String[] mMessages = new String[]{
            "Hello! Body!"."User is not online! Please contact me later!"."What's your name, please?"."Bravo, brother!."Is it true that Google doesn't need science to get online?"."Hurt in the heart, iron!!"
    };


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

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

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

    private class TCPServer implements Runnable {

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(8888);
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
            while(! isServiceDestroyed) { // receive request from client try { final Socket client = serverSocket.accept(); Log.d(TAG,"=============== 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 {
        //receive message
        BufferedReader in = new BufferedReader(
                new InputStreamReader(client.getInputStream()));
        //send message
        PrintWriter out = new PrintWriter(
                new BufferedWriter(
                        new OutputStreamWriter(
                                client.getOutputStream())),true);
        out.println("Welcome to the chat room!");

        while(! isServiceDestroyed) { String str = in.readLine(); Log.d(TAG,"message from client: " + str);
            if (str == null) {
                return;
            }
            Random random = new Random();
            int index = random.nextInt(mMessages.length);
            String msg = mMessages[index];
            out.println(msg);
            Log.d(TAG, "send Message: "+ msg); } out.close(); in.close(); client.close(); }}Copy the code

5.7. Select appropriate IPC methods