IPC進(jìn)程間通信

IPC相關(guān)概念

IPC是Inter-Process-Communication的縮寫,即進(jìn)程間通信。Binder是Android中最具特色的進(jìn)程間通信方式。

  • 多進(jìn)程引用方式

Android中引用多進(jìn)程的唯一方式:對四大組件在AndroidMenifest.xml中指定android:process屬性。

<activity
      android:name=".ui.activity.TestActvity"
      android:screenOrientation="portrait"
      android:process=":test"
      android:windowSoftInputMode="stateAlwaysHidden|adjustPan"/>

沒有指定android:process屬性就是默認(rèn)進(jìn)程,進(jìn)程名為包名。

兩種進(jìn)程名寫法:
android:process=": test",該進(jìn)程名前附上包名即為其完整進(jìn)程名,即com.zjrb.sjzsw: test。表示該進(jìn)程為應(yīng)用私有,不可共享。
android:process="com.zjrb.sjzsw.test",該名即為完整進(jìn)程名,屬于全局進(jìn)程,其他組件可通過ShareUID方式共享進(jìn)程。

  • 序列化和反序列化

Serializable是Java提供的標(biāo)準(zhǔn)序列化接口,可在類的聲明中指定標(biāo)識(靜態(tài)常量)用于反序列化時(shí)標(biāo)識是同一個(gè)數(shù)據(jù)源。

//推薦手動(dòng)指定serialVersionUID值,避免類結(jié)構(gòu)改變引起hash值改變,進(jìn)而反序列化失敗。
private static final long serialVersionUID = 8711368828010083044L;

序列化和反序列化的過程即為將對象實(shí)例的數(shù)據(jù)寫入文件和從文件讀取的過程。原理同下:


靜態(tài)變量屬于類不屬于對象實(shí)例,不參與序列化過程;用transient關(guān)鍵字標(biāo)記的變量不參與序列化過程。

Parcelable和Serializable的區(qū)別:

  • Serializable是Java中的序列化接口,其使用起來簡單但是開銷很大,序列化和反序列化過程中需要大量的I/O操作。
  • Parcelable是Android中的序列化接口,更適合運(yùn)用在Android平臺上,缺點(diǎn)是使用起來麻煩些,但效率高。
    Parcelable主要運(yùn)用在內(nèi)存序列化上,Serializable將對象序列化到存儲設(shè)備中或者將序列化后通過網(wǎng)絡(luò)傳輸比較方便。

在AIDL進(jìn)程間通信中,實(shí)體類序列化宜采用Parcelable,實(shí)測暫不支持Serializable方式的序列化。

Binder機(jī)制

  • 進(jìn)程隔離

進(jìn)程之間無法直接進(jìn)行交互的,操作系統(tǒng)為了保證自身的安全穩(wěn)定性,將系統(tǒng)空間分為內(nèi)核空間用戶空間。

內(nèi)核空間是系統(tǒng)內(nèi)核運(yùn)行的空間,內(nèi)核空間數(shù)據(jù)可共享。
用戶空間是用戶程序運(yùn)行的空間,用戶空間數(shù)據(jù)不共享。因此進(jìn)程間通信是靠內(nèi)核空間驅(qū)動(dòng)的。

當(dāng) Client 向 Server 發(fā)起 IPC 請求時(shí),Client 會先將請求數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間。系統(tǒng)會將內(nèi)核空間中的數(shù)據(jù)拷貝到 Server端用戶空間的緩存中。這樣就成功的將 Client 進(jìn)程中的請求數(shù)據(jù)傳遞到了 Server 進(jìn)程中。

  • Binder內(nèi)在原理

Binder是一種架構(gòu),提供了服務(wù)端接口、Binder驅(qū)動(dòng)、客戶端接口三個(gè)模塊。

ServiceManager:是一個(gè)獨(dú)立的進(jìn)程,管理各種系統(tǒng)服務(wù)??蛻舳苏{(diào)用Service之前,會向ServiceManager查詢該服務(wù)是否存在,若存在則返回該Service的引用。

Binder描述符:在Binder內(nèi)部,其唯一標(biāo)識由當(dāng)前Binder的類名全路徑表示。

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags):
該方法運(yùn)行在服務(wù)端的Binder線程池中,當(dāng)客戶端發(fā)起跨進(jìn)程請求時(shí),遠(yuǎn)程請求會在內(nèi)核空間的驅(qū)動(dòng)交由此方法來處理。

  • code 服務(wù)端通過code確定客戶端請求的目標(biāo)方法;
  • data 從data中取出目標(biāo)方法所需的參數(shù),如果有的話;
  • reply 目標(biāo)方法執(zhí)行完畢后,會向reply中寫入返回值,如果有的話;
  • flags 表示是否需要阻塞等待返回結(jié)果,0或FLAG_ONEWAY。

Binder通信流程如下:

服務(wù)端Binder對象創(chuàng)建后(同時(shí)Binder驅(qū)動(dòng)中也會創(chuàng)建一個(gè)Binder對象),內(nèi)部會啟動(dòng)一個(gè)隱藏的線程,該線程會接收Binder驅(qū)動(dòng)發(fā)送的消息,之后會執(zhí)行Binder對象的onTransact()函數(shù),并按照該函數(shù)的參數(shù)執(zhí)行不同的服務(wù)代碼。

客戶端通過Binder驅(qū)動(dòng)中的Binder對象,該對象名為mRemote,調(diào)用其transact()方法,向服務(wù)端發(fā)送消息,并掛起客戶端當(dāng)前線程,待接收到服務(wù)端執(zhí)行完指定函數(shù)后的通知,客戶端線程恢復(fù)喚醒狀態(tài)。

為什么用Binder機(jī)制?

  • 性能:Binder機(jī)制下數(shù)據(jù)拷貝只需一次,而管道、消息隊(duì)列和socket都需要兩次,共享內(nèi)存不需要內(nèi)存拷貝,略優(yōu)于Binder。
  • 穩(wěn)定性:Binder基于C/S架構(gòu),雙端獨(dú)立解耦,而共享內(nèi)存需要處理并發(fā)問題。
  • 安全性:Android為每個(gè)應(yīng)用程序分配了鑒別進(jìn)程身份的UID,C端將指令發(fā)送S端,S端會根據(jù)權(quán)限控制執(zhí)行策略。

進(jìn)程間通信方式

Bundle

Bundle實(shí)現(xiàn)了Parcelable接口,支持activity/service/receiver在進(jìn)程間傳遞數(shù)據(jù)(ContentProvider默認(rèn)支持進(jìn)程間通信),并通過Intent發(fā)送。
Bundle支持的數(shù)據(jù)類型就是進(jìn)程間通信的數(shù)據(jù)類型,即數(shù)據(jù)實(shí)現(xiàn)序列化。

最簡單的進(jìn)程間數(shù)據(jù)傳遞方式,推薦。

文件共享

兩個(gè)進(jìn)程通過讀寫同一個(gè)文件來交換數(shù)據(jù),需要處理好線程同步問題,避免并發(fā)沖突。
SharedPreferences是采用XML文件來存儲鍵值對,是帶有緩存的文件存儲,不推薦在進(jìn)程間傳遞數(shù)據(jù)。

適合對同步要求不高的場景。

Messenger

Messenger底層是通過封裝AIDL實(shí)現(xiàn)進(jìn)程間通信,通過帶有Handler的客戶端接收從服務(wù)端傳回的消息。
優(yōu)點(diǎn):支持一對多通信;支持實(shí)時(shí)通信;無并發(fā)問題;
缺點(diǎn):不支持RPC(遠(yuǎn)程過程調(diào)用);數(shù)據(jù)類型單一(僅支持Bundle支持的數(shù)據(jù)類型);

簡單的消息傳輸,不支持RPC,適合于低并發(fā)的一對多即時(shí)通信的場景。

  • 案例解析:客戶端client給服務(wù)端發(fā)消息,服務(wù)端server收到消息后回復(fù)客戶端。

客戶端:

public class MessengerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.jinzifu.myserver.Messenger");
                intent.setPackage("com.jinzifu.myserver");
                bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
            }
        });
    }

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //① 建立通信  客戶端通過Messenger向服務(wù)端發(fā)消息
            Messenger messenger = new Messenger(service);

            Bundle bundle = new Bundle();
            bundle.putString("fromClient", "這是客戶端對你家人的問候。");
            Message message = new Message();
            message.what = 102;
            message.setData(bundle);
            //② 傳遞客戶端Messenger,用于接收服務(wù)端傳回的消息
            message.replyTo = mMessenger;
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    Messenger mMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 101:
                    Bundle bundle = msg.getData();
                    String content = bundle.getString("fromServer");
                    Log.d("jinzifu", "來自服務(wù)端的消息:" + content);
                    break;
            }
        }
    });
}
  • 建立通信 客戶端通過Messenger向服務(wù)端發(fā)消息;
  • 向服務(wù)端傳遞客戶端帶有Handler的Messenger,用于接收服務(wù)端傳回的消息;

服務(wù)端:

<service android:name=".MessengerService">
        <intent-filter>
            <action android:name="com.jinzifu.myserver.Messenger" />
        </intent-filter>
</service>
public class MessengerService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new Messenger(new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 102:
                        Bundle bundle = msg.getData();
                        String content = bundle.getString("fromClient");
                        Log.d("jinzifu", "來自客戶端的消息:" + content);
                        Messenger messenger = msg.replyTo;

                        Bundle bundle1 = new Bundle();
                        bundle1.putString("fromServer", "已收到,回敬與你。");
                        Message message = new Message();
                        message.what = 101;
                        message.setData(bundle1);
                        try {
                            //③ 獲得客戶端的Messenger,并回傳消息
                            messenger.send(message);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        break;
                }
            }
        }).getBinder();
    }
}
  • 獲得客戶端的Messenger,并回傳消息;

Log日志

com.jinzifu.myserver D/jinzifu: 來自客戶端的消息:這是客戶端對你家人的問候。
com.jinzifu.myclient D/jinzifu: 來自服務(wù)端的消息:已收到,回敬與你。

AIDL

AIDL 即Android Interface definition language的縮寫,是Android接口定義語言。是系統(tǒng)內(nèi)部提供的一種快速實(shí)現(xiàn)Binder的工具而已,也可手動(dòng)實(shí)現(xiàn)。在Android中常用的通信方式是基于AIDL的遠(yuǎn)程Service。

基于AIDL的遠(yuǎn)程Service是非常復(fù)雜的通信方式,需考慮線程阻塞、權(quán)限驗(yàn)證、死亡監(jiān)聽等。

AIDL支持的所有類型(未列類型暫不支持)

注意:

  • AIDL中傳遞的接口,不是普通的接口,必須是AIDL接口。且接口中只支持方法,不支持聲明靜態(tài)常量。
  • AIDL中自定義的Parcelable對象和AIDL對象必須要顯式的import進(jìn)來,不管是否在同一個(gè)包內(nèi)。
  • AIDL中除了基本數(shù)據(jù)類型,其他支持的類型的參數(shù)都要標(biāo)注方向:in、out和inout。
  • 所有的AIDL接口都繼承于 android.os.IInterface接口。
interface BaseDataAidlInterface {
   /**
    * 類型示例,刪除即可
    */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
        double aDouble, String aString,in List<String> list);
}

in、out和inout的區(qū)別:

  • in表示輸入型參數(shù),數(shù)據(jù)只能由客戶端傳向服務(wù)端,服務(wù)端對數(shù)據(jù)的修改不會影響到客戶端。
  • out表示輸出型參數(shù),數(shù)據(jù)只能從服務(wù)端傳向客戶端,即使客戶端通過方法入?yún)⑾蚍?wù)端傳入對象,值也為空的。
  • inout表示輸入輸出型參數(shù)。

in/out標(biāo)簽允許Binder跳過編組(序列化、傳輸、接收和反序列化)步驟,以獲得更好的性能。

ContentProvider

ContentProvider是Android提供的專門為不同應(yīng)用進(jìn)行數(shù)據(jù)共享的方式。主要以表格的形式組織數(shù)據(jù),還支持圖片、視頻等文件數(shù)據(jù)。
優(yōu)點(diǎn):在數(shù)據(jù)源訪問方面功能強(qiáng)大,支持一對多并發(fā)數(shù)據(jù)共享,可通過Call方法擴(kuò)展其他操作。
缺點(diǎn):可以理解為受約束的AIDL,主要提供數(shù)據(jù)源的CRUD(create read update delete)操作,要注意SQLite注入和目錄遍歷的漏洞。

Android封裝好的進(jìn)程間數(shù)據(jù)共享方式,推薦,不支持RPC。

  • 案例解析:服務(wù)端操作數(shù)據(jù)庫提供數(shù)據(jù)給客戶端。

URI統(tǒng)一資源標(biāo)識符:是標(biāo)識ContentProvider中資源唯一性的符號。

Uri uri = Uri.parse("content://com.jzf.progress/book/1");
  • content:主題名,是ContentProvider的URI前綴,系統(tǒng)規(guī)定的;
  • com.jzf.progress:授權(quán)信息,ContentProvider的唯一標(biāo)識符;
  • book:表名,ContentProvider指向數(shù)據(jù)庫中的具體表名;
  • 1:記錄,表中的某個(gè)記錄,如果沒指定,默認(rèn)返回的是全部記錄;

Android提供了用于操作Uri的工具類UriMatcher,使用UriMatcher.match(uri)對輸入的Uri進(jìn)行匹配,如果匹配成功就返回匹配碼,匹配碼是調(diào)用addURI()方法傳入的第三個(gè)參數(shù)。

ContentResolver 內(nèi)容解析器:ContentResolver提供了與ContentProvider一樣的增刪改查方法,統(tǒng)一管理不同ContentProvider與外部進(jìn)程的通信。ContentProvider#notifyChange方法通知外界訪問者ContentProvider中的數(shù)據(jù)有更新。

服務(wù)端

public class EventProvider extends ContentProvider {
    private static final String AUTHORITY = "com.jinzifu.myserver.EventProvider";
    //① 關(guān)聯(lián)Uri和Uri_Code,識別外界要操作的表
    private static final int EVENT_URI_CODE = 101;
    private static final UriMatcher uriMathcher =
            new UriMatcher(UriMatcher.NO_MATCH);
    private static final String TAG = "jinzifu";

    static {
        uriMathcher.addURI(AUTHORITY, "event", EVENT_URI_CODE);
    }

    private Context mContext;
    private SQLiteDatabase mSQLiteDatabase;

    /**
     * ① 一般用于創(chuàng)建數(shù)據(jù)庫或升級等操作,外界調(diào)用getContentResolver()時(shí)回調(diào)。
     * onCreate 運(yùn)行在UI線程中,其他方法運(yùn)行在binder線程中。
     *
     * @return fasle則表示provider創(chuàng)建失敗
     */
    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate: EventProvider初始化");
        mContext = getContext();
        mSQLiteDatabase = new MySQLiteHelper(mContext, "",
                null, 0).getWritableDatabase();
        new Thread(new Runnable() {
            @Override
            public void run() {
                //② 數(shù)據(jù)庫操作不應(yīng)該放在主線程,數(shù)據(jù)庫操作命令需掌握
                mSQLiteDatabase.execSQL("delete from "
                        + MySQLiteHelper.TABLE_EVENT);
                mSQLiteDatabase.execSQL("insert into "
                        + MySQLiteHelper.TABLE_EVENT
                        + " values(1,'jzf',100);");
            }
        }).start();
        return true;
    }

    /**
     * ⑤ 查詢數(shù)據(jù)
     *
     * @param uri           根據(jù)uri查詢數(shù)據(jù)庫中對應(yīng)表的數(shù)據(jù)
     * @param projection    選擇符合條件的列查詢數(shù)據(jù),傳null則查詢所有列
     * @param selection     選擇符合條件的行查詢數(shù)據(jù),傳null則查詢所有行
     * @param selectionArgs 類似selection
     * @param sortOrder     對查詢結(jié)果排序,傳null則為默認(rèn)排序,也可無序
     * @return 返回一個(gè)Cursor對象,用后須關(guān)閉,避免內(nèi)存泄露
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri,
                        @Nullable String[] projection,
                        @Nullable String selection,
                        @Nullable String[] selectionArgs,
                        @Nullable String sortOrder) {
        String table = getTableFormUri(uri);
        if (TextUtils.isEmpty(table)) return null;
        Log.d("jinzifu", "EventProvider開始查詢");
        return mSQLiteDatabase.query(table,
                projection,
                selection,
                selectionArgs,
                null,
                null,
                sortOrder);
    }

    private String getTableFormUri(Uri uri) {
        switch (uriMathcher.match(uri)) {
            case EVENT_URI_CODE:
                return MySQLiteHelper.TABLE_EVENT;
        }
        return null;
    }


    /**
     * 返回指定內(nèi)容的媒體類型
     *
     * @param uri
     * @return MIME類型 如圖片、視頻等,可為null
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    /**
     * ② 添加數(shù)據(jù)
     *
     * @param uri    根據(jù)uri插入數(shù)據(jù)庫中對應(yīng)表的數(shù)據(jù)
     * @param values ContentValues內(nèi)部使用HashMap存儲數(shù)據(jù)的,
     *               key表示列名,value表示行名,如果value為空,在表中是空行,無內(nèi)容。
     * @return 返回這條數(shù)據(jù)的uri
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri,
                      @Nullable ContentValues values) {
        //② 與其他方法均屬于并發(fā)編程的,要做好線程同步??

        //③ SQLiteDatabase內(nèi)部對數(shù)據(jù)庫的操作是有同步處理的,
        // 但多個(gè)SQLiteDatabase對象對ConentProvider并發(fā)操作無同步處理。
        String table = getTableFormUri(uri);
        if (TextUtils.isEmpty(table)) return null;
        mSQLiteDatabase.insert(table, null, values);
        mContext.getContentResolver().notifyChange(uri, null);
        Log.d(TAG, "insert: 插入數(shù)據(jù)成功");
        return uri;
    }

    /**
     * ③ 刪除數(shù)據(jù)
     *
     * @param uri           根據(jù)uri刪除數(shù)據(jù)庫中對應(yīng)表的數(shù)據(jù)
     * @param selection     選擇符合條件的行數(shù)據(jù)刪除
     * @param selectionArgs 類似selection
     * @return 返回被刪除的行數(shù)
     */
    @Override
    public int delete(@NonNull Uri uri,
                      @Nullable String selection,
                      @Nullable String[] selectionArgs) {
        String table = getTableFormUri(uri);
        if (TextUtils.isEmpty(table)) return 0;
        int count = mSQLiteDatabase.delete(table,
                selection,
                selectionArgs);
        if (count > 0) mContext.getContentResolver().notifyChange(uri, null);
        return count;
    }

    /**
     * ④ 更改數(shù)據(jù)
     *
     * @param uri           根據(jù)uri修改數(shù)據(jù)庫中對應(yīng)表的數(shù)據(jù)
     * @param values        同insert中的ContentValues用法,若value為空,則會將原來的數(shù)據(jù)置空
     * @param selection     選擇符合條件的行數(shù)據(jù)修改
     * @param selectionArgs 類似selection
     * @return 返回更新的行數(shù)
     */
    @Override
    public int update(@NonNull Uri uri,
                      @Nullable ContentValues values,
                      @Nullable String selection,
                      @Nullable String[] selectionArgs) {
        String table = getTableFormUri(uri);
        if (TextUtils.isEmpty(table)) return 0;
        int row = mSQLiteDatabase.update(table, values, selection, selectionArgs);
        if (row > 0) mContext.getContentResolver().notifyChange(uri, null);
        return row;
    }
}
public class MySQLiteHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "provider_test.db";
    private static final int DB_VERSION = 1;

    public static final String TABLE_EVENT = "event";
    public static final String CREATE_EVENT_TABLE =
            "CREATE TABLE IF NOT EXISTS " + TABLE_EVENT
                    + "(_id INTEGER PRIMARY KEY,name TEXT,count INTEGER)";

    public MySQLiteHelper(@Nullable Context context,
                          @Nullable String name,
                          @Nullable SQLiteDatabase.CursorFactory factory,
                          int version) {
        super(context, DB_NAME, null, DB_VERSION);
    }

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

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

    }
}
<permission android:name="com.jinzifu.myserver.permission.ACCESS_EVENT_PROVIDER"
        android:protectionLevel="normal"/>
        
<!--⑤ android:authorities=""ContentProvider的唯一標(biāo)識,-->
        <!--android:writePermission="" 寫權(quán)限,-->
        <!--android:readPermission=""讀權(quán)限,若讀寫權(quán)限都要求,則上面都要聲明-->
        <!--android:permission=""全部權(quán)限-->
        <!--android:exported="true"供外部程序調(diào)用-->
        <provider
            android:name=".EventProvider"
            android:authorities="com.jinzifu.myserver.EventProvider"
            android:exported="true"
            android:permission="com.jinzifu.myserver.permission.ACCESS_EVENT_PROVIDER" />

客戶端

public class ProviderActivity extends AppCompatActivity {
    private static final String TAG = "jinzifu";
    private Uri uri =
            Uri.parse("content://com.jinzifu.myserver.EventProvider/event");
    private ContentObserver mContentObserver;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.ok)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        ContentValues contentValues = new ContentValues();
                        //① 數(shù)據(jù)插入時(shí)就知道數(shù)據(jù)的列名,這是SQLite注入的漏洞點(diǎn)
                        contentValues.put("_id", 2);
                        contentValues.put("name", "jzf2");
                        contentValues.put("count", 101);

                        getContentResolver().insert(uri, contentValues);
                    }
                });

        //監(jiān)聽ContentProvider的數(shù)據(jù)變化回調(diào)
        getContentResolver().registerContentObserver(
                uri,
                true,
                mContentObserver = new ContentObserver(new Handler()) {
                    @Override
                    public void onChange(boolean selfChange, Uri uri) {
                        super.onChange(selfChange, uri);
                        Log.d(TAG, "onChange: 監(jiān)聽到provider數(shù)據(jù)變化");

                        Cursor cursor = getContentResolver().query(
                                uri,
                                new String[]{"name"},
                                null,
                                null,
                                null);
                        if (cursor == null) return;
                        while (cursor.moveToNext()) {
                            Log.d(TAG, "name: "
                                    + cursor.getString(
                                    cursor.getColumnIndex("name")));
                        }

                        //① Cursor用后需要及時(shí)回收,Cursor.close()。
                        cursor.close();
                    }
                });
    }

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

Log日志

com.jinzifu.myserver D/jinzifu: insert: 插入數(shù)據(jù)成功
com.jinzifu.myclient D/jinzifu: onChange: 監(jiān)聽到provider數(shù)據(jù)變化
com.jinzifu.myserver D/jinzifu: EventProvider開始查詢
com.jinzifu.myclient D/jinzifu: name: jzf
com.jinzifu.myclient D/jinzifu: name: jzf2

Socket

Socket稱為套接字,分為TCP協(xié)議的流式套接字和UDP協(xié)議的用戶數(shù)據(jù)報(bào)套接字。進(jìn)程間可以通過Socket實(shí)現(xiàn)端到端的通信,且Socket支持任意字節(jié)流。

優(yōu)點(diǎn):功能強(qiáng)大,可通過網(wǎng)絡(luò)傳輸字節(jié)流,支持一對多并發(fā)實(shí)時(shí)通信。
缺點(diǎn):實(shí)現(xiàn)細(xì)節(jié)稍微有點(diǎn)繁瑣,不支持直接的RPC,適用于網(wǎng)絡(luò)數(shù)據(jù)交換(即需要網(wǎng)絡(luò)支持)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容