Android点将台:你敢摸我猫 [- IPC -]

3,770 阅读13分钟

零、前言:本文源码:GitHub

1.先在视觉上瞄一下进程和线程的区别


2.再从感性上类比一下进程和线程
如果手机是地球,进程就像一家公司,公司使用着地球的资源,也在地球上扮演一个独立的个体,实现自己特有的功能与价值。
而线程就像公司里的人,可以共享公司的公共资源,处理属于自己的任务,实现自身的功能与价值。
可以说进程(公司)是给线程(人)一个运行(工作)的环境。于此同时进程也获得了它的地位。

所以一个进程至少要一个线程来完成任务。进程销毁后,里面的线程也就失业拜拜了。
比如某公司的人(线程)集体罢工(崩溃),那公司无论曾经叫什么,都没有意义。公司(进程)倒闭了,再多的线程(人)也没卵用。

多进程就像若干个公司联盟做一个项目,这时候各个公司的内部资源(静态成员、单例等)就不再适用,
就像别的公司人到你公司吃你的零食,敲你键盘,摸你猫,你给吗? 不给,坚决不给。
然后那人非要吃你零食,敲你键盘,摸你猫,还搞出个职位叫IPC,说什么跨进程间通信。TM说白了就是专门抢你零食,抢你猫,你说气不气人。

3.最后走一波概念
IPC(Inter-Process Communication): 进程间通信或者跨进程通信
进程:指的一个执行单元,在PC和移动设备上指的是一个程序或者一个应用。
线程:在操作系统中,线程是CPU调度的最小单元,也是一种有限的系统资源。
进程与线程关系:一个进程可以包含多个线程,因此进程和线程是包含被包含的关系。

二、如何在一个应用创建多个进程

1.三个测试Activity

三个按钮跳转三个Activity,布局就不贴了。只贴一个MainActivity0,其他两个MainActivity1,MainActivity2

class MainActivity0 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        title="MainActivity0"
        to_one.setOnClickListener {
            startActivity(Intent(this, MainActivity0::class.java))
        }
        to_two.setOnClickListener {
            startActivity(Intent(this, MainActivity1::class.java))
        }
        to_three.setOnClickListener {
            startActivity(Intent(this, MainActivity2::class.java))
        }
    }
}

2.AndroidManifest.xml配置文件

私有进程:有:---------- 全局进程:没有: 名字可以随便取,只要唯一

<activity android:name=".MainActivity1"
          android:process=":ipc">
</activity>
<activity android:name=".MainActivity2"
          android:process="com.toly1994.ipc.test">
</activity>

三、多进程与单进程的区别

1.打开Activity1时

不加的话,直接通过窗口管理器来显示Activity1


加的话,会在孵化一个进程。zygote64的日志很多,下面只是一小部分。
不清楚Activity启动和View加载过程的小伙伴,可以看一下这个日志,也许会有帮助
比如下面完美呈现了LayoutInflater是怎么运行的,再跟着源码走一走,你会有所收获


然后发现确实是多了两个,名字也能对应上


2.Application的多次实例化

既然开一个进程会孵化一次,ActivityThread的main方法被触发,Application自然会被新建
喵了个咪的,创建了三个,一个进程一个。这显然值得注意,自定义Application初始化第三方库什么的

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: 创建了小猫土土");
    }
}

3.静态成员变量无法在不同进程获取
public class CatManager {
    public static Cat cat = new Cat();

    public CatManager() {
        cat = new Cat();
        cat.color = "灰色" + Math.random();
        cat.name = "土土";
    }
}

---->[MainActivity0#oncreate]------------------
CatManager()

---->[MainActivity1#oncreate]------------------
Log.e("CatManager", ": "+CatManager.cat.color);//null 

|--- 说明在MainActivity1里已经初始化的静态成员变量无法在MainActivity2(另一个进程)使用
|--- 如果将MainActivity2的process去掉可以打印:灰色0.22701789806635642
|--- 这就尴尬了,我的唯一玩到666的单例肿么办?

4.单例模式会怎么样?

新建一个Cat(猫)和CatManager(铲屎官)的类

---->[Cat]------------------------------------
public class Cat {
    public String name;
    public String color;

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

---->[CatManager]------------------------------------
public class CatManager {
    private volatile static CatManager sCatManager;
    private static Cat cat=new Cat();

    private CatManager() {
    }

    public static CatManager newInstance() {
        if (sCatManager == null) {
            synchronized (CatManager.class) {
                if (sCatManager == null) {
                    sCatManager = new CatManager();
                    Log.e("CatApplication", "newInstance: ");

                    cat.color = "灰色"+Math.random();
                    cat.name = "土土";
                }
            }
        }
        return sCatManager;
    }

    public Cat getCat() {
        return cat;
    }
}

---->[CatApplication]------------------------------------
public class CatApplication extends Application {
    private static final String TAG = "CatApplication";

    @Override
    public void onCreate() {
        super.onCreate();
        CatManager manager = CatManager.newInstance();
        Log.e("CatApplication", manager.getCat().toString());
    }
}
2019-05-08 10:18:02.482 25524-25524/? E/CatApplication: newInstance: 
2019-05-08 10:18:02.482 25524-25524/? E/CatApplication: Cat{name='土土', color='灰色0.8695394451026908'}
2019-05-08 10:18:04.761 25561-25561/com.toly1994.ipc:ipc E/CatApplication: newInstance: 
2019-05-08 10:18:04.761 25561-25561/com.toly1994.ipc:ipc E/CatApplication: Cat{name='土土', color='灰色0.9824119267379914'}
2019-05-08 10:18:07.096 25597-25597/com.toly1994.ipc.test E/CatApplication: newInstance: 
2019-05-08 10:18:07.096 25597-25597/com.toly1994.ipc.test E/CatApplication: Cat{name='土土', color='灰色0.18620946012650275'}

可见单例也没有卵用了,每次开启进程都会执行到newInstance,导致单例的失调。


5.小结:多进程带来的问题(老生常谈)
Application会多次创建:开启一个进程其实就等同于开多一个Application
静态成员和单例模式完全失效(处于不同的内存块(进程),拥有各自的副本) 
SharedPreferences的可靠性降低:因为SharedPreferences不支持两个进程同时去读写xml文件 
线程同步机制完全失效(同一差不多) 

三、IPC的几种形式

为了多公司联盟(多进程)间的和谐,现在决定牺牲猫,让它可以被过各公司(进程)共享

[1].通过Intent传递Bundle对象通信: 简单,数据类型局限,用于组件间传递数据
[2].使用共享文件通信: 简单,实时性差,不适合高并发
[3].使用Messenger通信: 支持一对多串行通信,支持实时通信,不支持RPC
[4].使用AIDL通信: 支持一对多并发通信,适用于,一对多通信且有RPC需求
[5].使用ContentProvider: 支持一对多并发数据共享
[6].使用Socket: 可以通过网络传输字节流,支持一对多并发实时通信

0.现在的CatManager类和Cat类

既然单例不能用,就不用。这里默认开局一只猫。Cat实现序列化接口Serializable

public class CatManager {
    private static List<Cat> cats = new ArrayList<>();
    public CatManager() {
        Cat tutu = new Cat();
        tutu.color = "灰色" + Math.random();
        tutu.name = "土土";
        add(tutu);
    }
    public void add(Cat cat) {
        cats.add(cat);
    }
    public Cat getCatAt(int index) {
        return cats.get(index);
    }
}

public class Cat implements Serializable {
    public String name;
    public String color;

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

1.IPC之Intent发送Bundle对象通信
1-1.Serializable序列化对象实现
---->[MainActivity0#oncreate]------------------
to_two.setOnClickListener {
    val cat = CatManager().getCatAt(0)
    val bundle = Bundle()//创建Bundle对象
    bundle.putSerializable("cat", cat)//把猫装到Bundle里,贴个标签cat
    val intent = Intent(this, MainActivity1::class.java)
    intent.putExtras(bundle)
    startActivity(intent)
}

---->[MainActivity1#oncreate]------------------
val cat = intent.extras?.get("cat") as Cat //把Bundle用打开标签cat,然后猫到手
Log.e("MainActivity1", ": " + cat.name)//MainActivity1可以对猫为所欲为,IPC 通信完成

注:当然你也可以直接通过Intent发送序列化(Serializable)对象,源码瞄一眼,都是通过Bundle的,并无本质区别

public @NonNull Intent putExtra(String name, Serializable value) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putSerializable(name, value);
    return this;
}

1-2.Parcelable序列化对象实现

Android里说Serializable,怎么能少得了同胞兄弟Parcelable呢,两者都是对象序列化的手段
两者的详细比较这里就不赘述,详见:Android点将台的Intent篇。什么是序列化和反序列化,个人理解如下:

比如我家有个大的衣柜(对象),现在要搬家,一下子搬不走,怎么办?
把每块板贴个标签,然后拆了,一块块摆好,然后就能运走了,这叫序列化。
然后到新家里,把板再一块块地拼起来,然后大衣柜(对象)就又回来了,这叫反序列化。

上面说的是物质对象的运输过程,那么信息/数据对象也可以这么类比,思想上是[怎么好运和拼装还原]
Serializable和Parcelable不影响序列化的概念,只是手段不同,就像是卡车运还是飞机运一样
Serializable和Parcelable接口代表这东西可拆,是一种可拆保证。要什么都乱拆,你家猫拆个试试。
下面直播拆猫:AS自动生成Parcelable相关代码,可以省我们一些事,but,请千万要了解一下他们是干嘛用的

---->[MainActivity0#oncreate]------------------
to_two.setOnClickListener {
    val cat = CatManager().getCatAt(0)
    val bundle = Bundle()//创建Bundle对象
    bundle.putParcelable("cat", cat)//把猫装到Bundle里,贴个标签cat
    val intent = Intent(this, MainActivity1::class.java)
    intent.putExtras(bundle)
    startActivity(intent)
}

---->[MainActivity1#oncreate]------------------
val cat = intent.extras?.get("cat") as Cat //把Bundle用打开标签cat,然后猫到手
Log.e("MainActivity1", ": " + cat.name)//MainActivity1可以对猫为所欲为,IPC 通信完成

2.IPC之文件共享进行通信

把对象写入文件,然后通过文件反序列化出对象,给MainActivity2
(文件读写无论是效率还是多线程的不行,所以这里只是了解一下)

---->[MainActivity0#oncreate]------------------
to_three.setOnClickListener {
    val cat = CatManager().getCatAt(0)
    val file = File(cacheDir, "cat.obj")
    val oos = ObjectOutputStream(FileOutputStream(file))
    oos.writeObject(cat)
    oos.close()
    startActivity(Intent(this, MainActivity2::class.java))
}

---->[MainActivity2#oncreate]------------------
val file = File(cacheDir, "cat.obj")
val ois = ObjectInputStream(FileInputStream(file))
val cat = ois.readObject() as Cat//反序列化生成对象
ois.close()
Log.e("MainActivity1", ": " + cat.name)//MainActivity1可以对猫为所欲为,IPC 通信完成

可能到这你觉得IPC不就是传个对象吗?好像没什么大不了的


3.IPC之Messenger通信
3-1:Messenger是什么?

从都构造函数来看,是和Binder有关

private final IMessenger mTarget;

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

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

3-2:可以获取一个Ibinder对象

如此看来mTarget即IMessenger类很像一个AIDL接口

public IBinder getBinder() {
    return mTarget.asBinder();
}

3-3:Messenger的使用

既然是Ibinder对象,可以用在绑定服务中。既然个公司(摸)的人都要猫,干脆来个服务端。
谁(客户端)想来摸一下都可以,核心是Messenger发送消息,Service里接收消息

---->[CatService]------------------
public class CatService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return new Messenger(new Handler(msg -> {
            //接收客户端数据/信息/对象
            String data = msg.getData().getString("request");
            Log.e("MessengerActivity", "handleMessage: " + data);
            
            //向客户端发送数据/信息/对象
            Messenger client = msg.replyTo;
            Message message = Message.obtain();
            Cat cat = new CatManager().getCatAt(0);
            Bundle bundle = new Bundle();//创建Bundle对象
            bundle.putParcelable("cat", cat);//把猫装到Bundle里,贴个标签cat
            message.setData(bundle);
            try {
                client.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            return false;
        })).getBinder();
    }
}

---->[将服务单独放在一个进程]--------------
<service android:name=".CatService"
         android:process="com.toly1994.ipc.service.cat"/>

---->[MessengerActivity]------------------
public class MessengerActivity extends AppCompatActivity {
    private static final String TAG = "MessengerActivity";
    private ServiceConnection conn;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        conn = new ServiceConnection() {
            private Messenger messenger;
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                messenger = new Messenger(service);
                Message message = Message.obtain();
                Bundle bundle = new Bundle();
                bundle.putString("request", "来自客户端:给我一只猫");
                message.setData(bundle);
                message.replyTo = new Messenger(new Handler((msg) -> {//服务端回应监听
                    Bundle data = msg.getData();
                    data.setClassLoader(getClass().getClassLoader());
                    Cat cat = (Cat) (data.get("cat"));
                    Log.e(TAG, "来自服务端: "+cat);
                    return false;
                }));
                try {
                    messenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
        bindService(new Intent(this, CatService.class), conn, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}

注意这里有一个坑,一开始读不出来异常如下,看名字感觉是类加载器的锅,貌似找不到Cat类
解决: data.setClassLoader(getClass().getClassLoader());

流程基本如下,并不知道两个Handler和三个Messenger,还有皮球一样乱跑的Message有没有把你绕晕


4.IPC之AIDL通信

这个不怎么想说,在Android点将台:金科玉律[-AIDL-]里已经讲得很详细了,为了完整一点,这里稍微再说一下吧。

4-1:定义接口:ICatService

简单一点,就定义一个喂养的方法


4-2:自动生成的类

类之间的关系基本如下:

package com.toly1994.ipc;
public interface ICatService extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.toly1994.ipc.ICatService {
        private static final java.lang.String DESCRIPTOR = "com.toly1994.ipc.ICatService";
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        //通过IBinder获取ICatService对象,绑定客户端时使用
        public static com.toly1994.ipc.ICatService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.toly1994.ipc.ICatService))) {
                return ((com.toly1994.ipc.ICatService) iin);
            }
            return new com.toly1994.ipc.ICatService.Stub.Proxy(obj);
        }

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

        @Override//此方法运行在服务端Binder线程池中,客户端发起跨进程请求时,远程请求通过系统底层封装后交由此方法处理
        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_feed: {
                    data.enforceInterface(descriptor);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    this.feed(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }
        //代理类,当客户端访问服务端时,客户端通过代理类生成一个ICatService对象 
        private static class Proxy implements com.toly1994.ipc.ICatService {
            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 void feed(java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_feed, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_feed = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public void feed(java.lang.String aString) throws android.os.RemoteException;
}


4-3:使用ICatService创建FeedCatService

优势在于客户端绑定服务是通过:ICatService.Stub.asInterface(service)获取ICatService对象
就可以调用ICatService接口方法。这样只暴露接口,可以限制客户端对小猫的操作,客户端即玩了,又不能为所欲为。

---->[FeedCatService]--------------------------------------
public class FeedCatService extends Service {
    private static final String TAG = "FeedCatService";
    private Binder binder = new ICatService.Stub() {
        @Override
        public void feed(String aString) throws RemoteException {
            Log.e(TAG, "feed: 你已喂" + aString + "给小猫土土了");
        }
    };

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

---->[将服务单独放在一个进程]---------------------------------
<service android:name=".FeedCatService"
         android:process="com.toly1994.ipc.service.feed.cat"/>
         
---->[AidlActivity]---------------------------------
public class AidlActivity extends AppCompatActivity {
    private ServiceConnection conn;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        conn = new ServiceConnection() {
            private ICatService catService;
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                catService = ICatService.Stub.asInterface(service);
                try {
                    catService.feed("鱼");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
        bindService(new Intent(this, FeedCatService.class), conn, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}

5.IPC之使用ContentProvider通信
5.1:数据库辅助
    private static String DATABASE_NAME = "cat.db";//数据库名
    private static int DATABASE_VERSION = 1;//数据库版本
    private volatile static CatDatabaseHelper sInstance;
    //双检锁单例
    public static synchronized CatDatabaseHelper getInstance(Context context) {
        if (sInstance == null) {
            synchronized (CatDatabaseHelper.class) {
                if (sInstance == null) {
                    sInstance = new CatDatabaseHelper(context);
                }
            }
        }
        return sInstance;
    }
    public CatDatabaseHelper(@Nullable Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        createSwordTable(db);
    }
    /**
     * 创建sword表
     *
     * @param db SQLiteDatabase
     */
    private void createSwordTable(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE cat (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                "name VARCHAR(32) NOT NULL," +
                "color VARCHAR(32) NOT NULL" +
                "); ");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

5.2:继承ContentProvider
public class CatContentProvider extends ContentProvider {
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int CAT_QUERY = 0;
    private static final int CAT_INSERT = 1;
    private static final int CAT_UPDATE = 2;
    private static final int CAT_DELETE = 3;
    private static final String TABLE_NAME = "cat";
    static {
        //给当前sUriMatcher添加匹配规则
        sUriMatcher.addURI("toly1994.com.cat", "query", CAT_QUERY);
        sUriMatcher.addURI("toly1994.com.cat", "insert", CAT_INSERT);
        sUriMatcher.addURI("toly1994.com.cat", "update", CAT_UPDATE);
        sUriMatcher.addURI("toly1994.com.cat", "delete", CAT_DELETE);
    }
    private SQLiteOpenHelper mOpenHelper;
    @Override
    public boolean onCreate() {
        mOpenHelper = CatDatabaseHelper.getInstance(getContext());
        return true;
    }
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == CAT_QUERY) {
            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
            return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
        } else {
            throw new IllegalStateException(" query Uri 错误");
        }
    }
    @Override
    public String getType(Uri uri) {
        return null;
    }
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == CAT_INSERT) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            Long insert = db.insert(TABLE_NAME, null, values);
            //uri:数据发送变化,通过uri判断调用哪个内容观察者
            //第二个参数:内容观察者对象  如果传null 则注册了整个uri的内容观察者皆可以收到通知
            getContext().getContentResolver().notifyChange(uri, null);
            db.close();
            return Uri.parse(String.valueOf(insert));
        } else {
            throw new IllegalStateException("insert Uri 错误");
        }
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == CAT_DELETE) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            int delete = db.delete(TABLE_NAME, selection, selectionArgs);
            db.close();
            return delete;
        } else {
            throw new IllegalStateException("delete Uri  错误");
        }
    }
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        //进行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == CAT_UPDATE) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            int update = db.update(TABLE_NAME, values, selection, selectionArgs);
            db.close();
            return update;
        } else {
            throw new IllegalStateException("update Uri 错误");
        }
    }
}

---->[配置]---------------------------------------------
<provider android:authorities="toly1994.com.cat"
          android:name=".CatContentProvider"
          android:exported="true"
          android:process="com.toly1994.ipc.provider.cat"
/>

5.3:使用

这样不同的进程就可以通过getContentResolver来操作数据库了

public class ProviderActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        insert(getContentResolver());
    }
    /**
     * 插入测试
     *
     * @param resolver
     */
    private void insert(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.cat/insert");
        ContentValues values = new ContentValues();
        values.put("name", "土土");
        values.put("color", "灰色");
        resolver.insert(uri, values);
    }
}


6.使用Socket

Socket可以让两个设备间的通信,两个进程自然也不在话下
这里不深入,只是客户端发一句话,服务端接收一下

---->[SocketService]---------------------------------
public class SocketService extends Service {
    private static final String TAG = "SocketService";
    private boolean quit = false;
    
    @Override
    public void onCreate() {
        Log.e(TAG, "onCreate: ");
        FeedServer feedServer = new FeedServer();
        new Thread(feedServer).start();
        super.onCreate();
    }

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

    private class FeedServer implements Runnable {

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(8080);
                Log.e(TAG, "serverSocket onCreate: ");
            } catch (IOException e) {
                e.printStackTrace();
            }

            while (!quit) {
                try {
                    Socket socket = serverSocket.accept();
                    //接收客户端消息
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(socket.getInputStream()));
                    String s = br.readLine();
                    Log.e(TAG, "来自客户端: " + socket.getInetAddress() + "说:" + s);

                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

---->[将服务单独放在一个进程]---------------------------------
<service android:name=".SocketService"
         android:process="com.toly1994.ipc.service.socket.feed.cat"/>
         
---->[ServerActivity]---------------------------------
public class ServerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startService(new Intent(this, SocketService.class));
        findViewById(R.id.to_one).setOnClickListener(v -> {
            new Thread(() -> {
                Socket socket = null;
                try {
                    socket = new Socket("localhost", 8080);
                    //客户端请求
                    BufferedWriter bw = new BufferedWriter(
                            new OutputStreamWriter(socket.getOutputStream()));
                    bw.write("我要猫");
                    bw.flush();
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

        });

    }
}

OK,本文就先这样,《Android开发艺术探索》是本不错的书,有多瞄几眼的价值。