Android程式間的通訊
1. 多程式使用場景
1) 應用某些模組因為特殊需求需要執行在單獨程式中。如訊息推送,使訊息推送程式與應用程式能單獨存活,訊息推送程式不會因為應用程式程式crash而受影響。
2) 為加大一個應用可使用的記憶體,需要多程式來獲取多份記憶體空間。
2. 如何開啟多程式
給四大元件(Activity、Service、Receiver、ContentProvider)在AndroidMainfest中指定Android:process屬性指定。
- 如果程式以”:”開頭的程式,代表應用的私有程式,其他應用的元件不可以和它跑在同一個程式中;
- 程式不以”:”開頭的程式屬於全域性程式,其他應用可通過shareUID可以和它跑在同一個程式中。
- 若兩個不同應用設定了相同的shareUID,它們之間想共享資料,還需要有相同的簽名才可以。
3. 多程式會造成的問題
(1) 靜態成員和單例失效,資料同步失敗;
Android會為每一個應用/每一個程式分配一個獨立的虛擬機器,不同虛擬機器在記憶體分配上有不同的地址空間,這就導致不同虛擬機器中訪問同一個類物件會產生多個副本。
(2) 執行緒同步機制失效;
因為不同程式不是同一塊記憶體,不同程式鎖的不是同一物件。
(3) SharedPreferences可靠性下降;
SharedPreferences底層是通過讀/寫XML檔案實現的,併發寫可能會出問題,所以它不支援多個程式同時去執行寫操作,否則會導致一定機率的資料丟失。
(4) Application會建立多次;
當一個元件跑在一個新程式中,系統會給它重新分配獨立虛擬機器,這其實就是啟動一個應用的過程,故執行在不同程式中的元件屬於不同的虛擬機器和不同的Application。
4. 資料序列化
Intent和Binder傳輸資料時,或是物件持久化轉存/通過網路傳輸給其他客戶端時,需要使用Parcelable或Serializable將物件轉換成可以傳輸的形式。
(1) Serializable介面
Serializable是Java提供的一個序列化介面,它是一個空介面,想要某個類實現序列化,只需要相應類實現Serializable介面即可。Serializable是藉助ObjectOutputStream和ObjectInputStream實現物件的序列化和反序列化
一般在相應類實現Serializable類中還會定義一個final long型的serialVersionUID,不用它也能實現物件的序列化,它是用來輔助反序列化的。序列化時會把當前類的serialVersionUID寫入序列化檔案中,當反序列化系統會檢測檔案中的serialVersionUID,看它是否和當前類的serialVersionUID一致,如果一致說明序列化的類的版本和當前類的版本相同,這時可反序化成功;否則說明當前類和序列化的類相比發生了變換,如增加或減少某個成員變數,這時無法正常反序列化。
(2) Parcelable介面
在序列化過程中需要實現的功能有:
1) 序列化:由writeToParcel方法完成,最終通過Parcel中的一系列的write方法完成;
2) 反序列化:由CREATOR完成,內部標識瞭如何建立序列化物件和陣列,最終通過Parcel的一系列read方法來完成反序列化;
3) 內容描述符:由describeContents方法來完成,幾乎所有情況下該方法都返回0,僅噹噹前物件中存在檔案描述符時返回1。
(3)兩者區別
Serializable是Java中序列化介面,使用簡單但開銷大,序列和反序列化需要大量I/O操作;
Parcelable是Android中特有的序列化介面,使用複雜但開銷小,效率高,是Android推薦的序列化方法;
Parcelable主要用在記憶體序列化上,如果將物件序列化到儲存裝置或將物件序列化後通過網路傳輸,過程會比較複雜,建議使用Serializable。
5. Binder
Binder是什麼?
- Binder是Android中的一個類,它繼承了IBinder介面;
- 從IPC角度來說,Binder是Android中一種跨程式通訊的方式;
- Binder還可以理解為一種虛擬的物理裝置,它的裝置驅動是/dev/binder;
- 從AndroidFramework角度來說,Binder是ServiceManager連線各種Manager(ActivityManager, WindowManager)和相應ManagerService的橋樑;
- 從Android應用層來說,Binder是客戶端和服務端進行通訊的媒介,當bindService時,服務端會返回一個包含了服務端業務的Binder物件,通過這個Binder物件,客戶端就可以獲取服務端提供的服務或資料,這裡的服務包括普通服務和基於AIDL的服務。
6.android中跨程式通訊方式
6.1 結合Bundle使用Intent
在一個程式中啟動另一個程式的Activity, Service, Receiver元件時,可以使用Bundle附加上需要傳遞的訊息給遠端程式,並通過Intent傳送出去。
6.2 使用檔案共享
兩個程式通過讀/寫同一個檔案來交換資料,還可以序列化一個物件到檔案系統中,從另一個程式中恢復這個物件。它的實現原理是通過ObjectOutputStream將檔案寫入檔案中,再通過ObjectInputSteam將檔案恢復。通過檔案共享的方式有一定侷限性,如併發讀/寫,讀出的內容可能不是最新的,併發寫就可能導致資料混亂。因此儘量避免併發寫這種操作,或考慮用執行緒同步來限制多個執行緒的寫操作。檔案共享適合在對資料要求不高的程式之間通訊。
SharedPreferences是Android提供的一種輕量級儲存方案,它通過鍵值對方式儲存資料,底層它是採用XML來儲存鍵值對。由於系統對SharedPreferences的讀寫有一定的快取策略,即在記憶體中會有一份SharedPreferences檔案的快取,在多程式模式下,系統對它的讀/寫就變得不可靠,當面對高併發讀/寫訪問時,SharedPreferences會有很大機率會丟失資料。因此不建議在程式間通訊中使用SharedPreferences。
6.3 使用Messenger
Messenager可以在不同程式中傳遞Message物件,在Message中放入我們要傳遞的資料。它的底層實現是AIDL,下面是Messenger的兩個構造方法:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
不管是IMessenger還是Stub.asInterface,這種使用方法都表明它的底層是AIDL,它一次只處理一個請求,不存線上程同步問題。
實現步驟:
(1) 服務端程式
1) 定義一個Service用於客戶端的繫結,建立一個Handler,在handleMessage裡處理客戶端傳送過來的訊息。
//用ServiceHandler接收並處理來自於客戶端的訊息
private class ServiceHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if(msg.what == RECEIVE_MESSAGE_CODE){
Bundle data = msg.getData();
if(data != null){
String str = data.getString("msg");
}
//通過Message的replyTo獲取到客戶端自身的Messenger,
//Service可以通過它向客戶端傳送訊息
clientMessenger = msg.replyTo;
if(clientMessenger != null){
Message msgToClient = Message.obtain();
msgToClient.what = SEND_MESSAGE_CODE;
//可以通過Bundle傳送跨程式的資訊
Bundle bundle = new Bundle();
bundle.putString("msg", "你好,客戶端,我是MyService");
msgToClient.setData(bundle);
try{
clientMessenger.send(msgToClient);
}catch (RemoteException e){
e.printStackTrace();
Log.e("DemoLog", "向客戶端傳送資訊失敗: " + e.getMessage());
}
}
}
}
}
2) 通過Handler建立一個Messenger物件
//serviceMessenger是Service自身的Messenger,其內部指向了ServiceHandler的例項
//客戶端可以通過IBinder構建Service端的Messenger,從而向Service傳送訊息,
//並由ServiceHandler接收並處理來自於客戶端的訊息
private Messenger serviceMessenger = new Messenger(new ServiceHandler());
3) 在Service的onBind中通過Messenger.getBinder()返回底層的Binder物件。
@Override
public IBinder onBind(Intent intent) {
Log.i("DemoLog", "MyServivce -> onBind");
//獲取Service自身Messenger所對應的IBinder,並將其傳送共享給所有客戶端
return serviceMessenger.getBinder();
}
4) 註冊服務,讓其執行在單獨程式中
<service
android:name=".MyService"
android:enabled="true"
android:process=":remote" >
</service>
(2) 客戶端程式 1) 繫結服務端的Service
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
//客戶端與Service建立連線
Log.i("DemoLog", "客戶端 onServiceConnected");
//我們可以通過從Service的onBind方法中返回的IBinder初始化一個指向Service端的Messenger
serviceMessenger = new Messenger(binder);
isBound = true;
Message msg = Message.obtain();
msg.what = SEND_MESSAGE_CODE;
//此處跨程式Message通訊不能將msg.obj設定為non-Parcelable的物件,應該使用Bundle
//msg.obj = "你好,MyService,我是客戶端";
Bundle data = new Bundle();
data.putString("msg", "你好,MyService,我是客戶端");
msg.setData(data);
//需要將Message的replyTo設定為客戶端的clientMessenger,
//以便Service可以通過它向客戶端傳送訊息
msg.replyTo = clientMessenger;
try {
Log.i("DemoLog", "客戶端向service傳送資訊");
serviceMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
Log.i("DemoLog", "客戶端向service傳送訊息失敗: " + e.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
//客戶端與Service失去連線
serviceMessenger = null;
isBound = false;
Log.i("DemoLog", "客戶端 onServiceDisconnected");
}
};
Intent intent = new Intent();
ComponentName componentName = new ComponentName(packageName, serviceNmae);
intent.setComponent(componentName);
try{
Log.i("DemoLog", "客戶端呼叫bindService方法");
bindService(intent, conn, BIND_AUTO_CREATE);
}catch(Exception e){
e.printStackTrace();
Log.e("DemoLog", e.getMessage());
}
}
繫結成功後用服務端返回的IBinder物件建立一個Messenger,通過它向服務端傳送Message訊息。
如果需要服務端給客戶端傳送訊息,需要在Handler的handleMessage方法裡,根據客戶端傳送過來的Message.replyTo獲取到客戶端的Messenger物件,就可以向客戶端傳送訊息了。同時客戶端需要在服務連線的onServiceConnected方法中,將客戶端的Messenger物件通過Message.replyTo給收到服務端傳送過來的Message物件,這樣就實現雙方通訊。
6.4 AIDL
6.4.1 在服務端新建需要的AIDL類
(1)先新建一個Book.java實現Parcelable介面,表示一個圖書的資訊類;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Book.java
*/
public class Book implements Parcelable{
private String name;
private int price;
public Book(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
/**
* 引數是一個Parcel,用它來儲存與傳輸資料
* @param dest
*/
public void readFromParcel(Parcel dest) {
//注意,此處的讀值順序應當是和writeToParcel()方法中一致的
name = dest.readString();
price = dest.readInt();
}
//方便列印資料
@Override
public String toString() {
return "name : " + name + " , price : " + price;
}
}
(2) 新建一個Book.aidl,表示Book類在AIDL中的宣告;
// Book.aidl
//這個檔案的作用是引入了一個序列化物件 Book 供其他的AIDL檔案使用
//注意:Book.aidl與Book.java的包名應當是一樣的
//注意parcelable是小寫
parcelable Book;
(3) 新建一個IBookManger.aidl介面,裡面包含相應的方法;
// IBookManger.aidl
//匯入所需要使用的非預設支援資料型別的包
import com.lypeer.ipcclient.Book;
interface IBookManger {
//所有的返回值前都不需要加任何東西,不管是什麼資料型別
List<Book> getBooks();
Book getBook();
}
該方法儲存後,會在gen目錄下生成一個IBookManger.java類,它是系統為BookManger.aidl生成的Binder類,繼承android.os.IInterface,它自己也還是個介面。它包括以下內容:
1) 定義了一個String型的DESCRIPTOR,Binder的唯一標識;
2) asInterface(android.os.IBinder obj)方法,將服務端的Binder物件轉換成客戶端所需的AIDL介面型別物件,當客戶端和服務端處於同一程式,直接返回服務端的Stub物件本身,否則返回系統封裝後的Stub.proxy物件。
3) asBinder方法:返回當前Binder物件。
4) onTransact方法:執行在服務端的Binder執行緒池中,當客戶端發起遠端請求,遠端請求會通過系統封裝後交由此方法處理,該方法原型為public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服務端通過code可以確定客戶端請求的是哪一個方法,從data中取出目標方法所需的引數,執行相應方法後,將返回值寫入reply中,如果此方法返回false,客戶端會請求失敗。
5) IBookManger.aidl介面中宣告的方法的代理實現,此方法執行在客戶端,當客戶端呼叫此方法時,首先需要建立此方法需要的輸入型別Parcel物件_data, 輸出型別Parcel物件_reply和返回值物件,接著將引數資訊寫入_data中(若有引數的話),再呼叫transact方法發起RPC(遠端過程呼叫),當前執行緒掛起,服務端的onTransact方法會被呼叫,直到RPC過程返回後,當前執行緒繼續執行,並從_reply中取出RPC過程的返回結果;最後返回_reply中的資料。
6) 宣告瞭IBookManger.aidl中宣告的方法;
7) 宣告瞭相應的整型id分別用於標識宣告的方法,該id用於標識在transact過程中判斷客戶端請求的到底是哪個方法;
8) 宣告瞭一個類部類Stub,繼承android.os.Binder實現IBookManager.aidl中類介面,當客戶端和服務端處於同一程式,方法呼叫不會走跨程式的transact過程,若位於不同程式,則由Stub的內部代理Proxy,呼叫跨程式transact過程。
需要注意的是:客戶端發起請求時,當前執行緒會掛起直至服務端返回資料,若方法是一個耗時操作,不能在UI執行緒中發起此遠端請求。服務端的Binder方法執行在Binder執行緒池中,所以不管操作是否耗時,都應採用同步的方法去實現。
(4)linkToDeath unlinkToDeath
由於Binder執行在服務端程式中,如果服務端程式由於某種原因終止,這時客戶端服務端的Binder連線斷裂,會導致遠端呼叫失敗。但客戶端很可能不知道,解決此問題的辦法,利用Binder提供的兩個配對方法linkToDeath和unlinkToDeath,通過linkToDeath可以給Binder設定一個死亡代理,當Binder被終止時,我們會接收到通知,這時可通過重新發起請求恢復連線。
實現方法:
1) 先宣告一個DeathRecipient物件,它是一個介面,內部只有一個方法binderDied,我們需要實現該方法,當Binder死亡時,系統回撥binderDied方法,我們可以移出之前binder代理並重新建立遠端服務。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBookManager == null) {
return;
}
mBookManger.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManger = null;
//重新繫結遠端服務
}
};
2) 在客戶端繫結遠端服務成功後,給binder設定死亡代理;
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);
6.4.2 遠端服務端Serivce的實現
(1)Serivce建立
我們需要新建一個Service,稱為AIDLService,程式碼如下:
/**
1. 服務端的AIDLService.java
*/
public class AIDLService extends Service {
public final String TAG = this.getClass().getSimpleName();
//包含Book物件的list
private List<Book> mBooks = new ArrayList<>();
//由AIDL檔案生成的IBookManager
private final IBookManager.Stub mBookManager = new IBookManager.Stub() {
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "Book is null in In");
book = new Book();
}
//嘗試修改book的引數,主要是為了觀察其到客戶端的反饋
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//列印mBooks列表,觀察客戶端傳過來的值
Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
}
}
};
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName("Android開發藝術探索");
book.setPrice(28);
mBooks.add(book);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
return mBookManager;//在onBinder中返回服務端的Binder物件
}
}
(2)註冊服務
<service android:name="aidl.AIDLService"
android:process=":remote">
</service>
6.4.3 客戶端的實現
/**
2. 客戶端的AIDLActivity.java
*/
public class AIDLActivity extends AppCompatActivity {
//由AIDL檔案生成的Java類
private IBookManager mBookManager = null;
//標誌當前與服務端連線狀況的布林值,false為未連線,true為連線中
private boolean mBound = false;
//包含Book物件的list
private List<Book> mBooks;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
/**
* 按鈕的點選事件,點選之後呼叫服務端的addBookIn方法
*
* @param view
*/
public void addBook(View view) {
//如果與服務端的連線處於未連線狀態,則嘗試連線
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "當前與服務端處於未連線狀態,正在嘗試重連,請稍後再試", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研發錄In");
book.setPrice(30);
try {
mBookManager.addBook(book);//通過服務端的Binder物件調服務端方法
Log.e(getLocalClassName(), book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 嘗試與服務端建立連線
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("aidl.AIDLService");
intent.setPackage("com.xx.packagename");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);//繫結服務
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
mBookManager = IBookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();//呼叫服務端的getBooks方法
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
}
通過獲取服務端的Binder物件,就可以和服務端進行通訊了。
6.4.4 其他知識點
(1)跨程式listener介面
如果在服務端定宣告瞭介面,客戶端進行註冊和反註冊,會丟擲異常,無法反註冊,因為在多程式中,Binder會把客戶端傳過來的物件重新轉化成一個新物件,這樣雖然客戶端註冊和反註冊使用的是同一個物件,但是通過Binder傳遞到服務端卻變成兩個物件,物件的跨程式傳輸本質都是反序列化過程。
可以使用RemoteCallbackList,它是系統專門用來刪除跨程式listener介面。它實現原理利用底層Binder物件是同一個,只要遍歷服務端所有listener,找出和解註冊listener具有相同Binder物件的服務端listener,並把它刪除掉。同時RemoteCallbackList還有一個作用,當客戶端程式終止,它會移除客戶端所註冊的所有listener.
(2)許可權驗證
在註冊服務時新增permission許可權驗證,或是在onTransact方法中進行許可權驗證;
(3) AIDL訪問流程總結:
先建立一個Service和一個AIDL介面,再建立一個類繼承自AIDL介面中的Stub類並實現Stub中的抽象方法;在Service的onBind方法中返回這個物件,再在客戶端就可以繫結服務端service,建立連線後就可以訪問遠端服務端的方法了。
6.5 ContentProvider
和Messenger一樣,ContentProvider底層同樣是Binder.系統預置了很多ContentProvider,如通訊錄、日程表等,只需通過ContentResolver的query/update/insert/delete就可以跨程式訪問這些資訊。具體實現步驟如下:
1) 新建一個繼承自系統ContentProvider的Provider,並重寫onCreate, query, getType, insert, delete, update六個方法。
這六個程式執行在ContentProvider的程式中,除了onCreate由系統回撥執行在主執行緒中,其他五個方法執行在Binder執行緒池中。
2) 註冊Provider
<provider android:name=".provider.XXProvider"
android:authorities="com.xx.xx.provider"
android:permission="com.xx.PROVIDER"
android:process=":provider">
</provider>
3) 在另一個程式中訪問我們定義的ContentProvider
Uri uri = Uri.parse("content://com.xx.xx.provider");
getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri,)
4) ContentProvider資料來源發生變化時,可通過ContentResolver的notifyChange方法來通知外界資料發生改變,外界可通過ContentResolver的registerContentObserver方法來註冊觀察者,通過unregisterContentObserver方法來解除觀察者。
6.6 Socket
Socket分為流式套接字和使用者資料包套接字,分別是對應於網路的傳輸控制層中的TCP/UDP
TCP:面向連線的協議,穩定的雙向通訊功能,連線需要“三次握手”,提供了超時重傳機制,具有很高的穩定性;
UDP:面向無連線協議,不穩定的單向通訊功能,也可提供雙向通訊功能。效率高,不能保證資料一定能正確傳輸。
實現步驟:
1)服務端在新建一個Service,並建立TCP服務
@Override
public void onCreate() {
new Thread(new TcpServer()).start();//阻塞監聽客戶端連線
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
throw null;
}
private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket;
try {
//監聽8688埠
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
return;
}
while (!isServiceDestroyed) {
try {
// 接受客戶端請求,並且阻塞直到接收到訊息
final Socket client = serverSocket.accept();
new Thread() {
@Override
public void run() {
try {
//有訊息接收到,新開一個執行緒進行處理訊息
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
// 用於接收客戶端訊息,將客戶端的二進位制資料流格式轉成文字格式
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 用於向客戶端傳送訊息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
out.println("您好,我是服務端");
while (!isServiceDestroyed) {
String str = in.readLine();//通過資料流的readLine,可直接變成文字格式
Log.i("moon", "收到客戶端發來的資訊" + str);
if (TextUtils.isEmpty(str)) {
//客戶端斷開了連線
Log.i("moon", "客戶端斷開連線");
break;
}
String message = "收到了客戶端的資訊為:" + str;
// 從客戶端收到的訊息加工再傳送給客戶端
out.println(message);
}
out.close();//關閉流
in.close();
client.close();
}
2)註冊服務
<service
android:name=".SocketServerService"
android:process=":remote" />
3) 客戶端先建立連線服務端socketSocket socket = null;
while (socket == null) {
try {
//選擇和伺服器相同的埠8688
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
} catch (IOException e) {
SystemClock.sleep(1000);
}
}
4) 客戶端和服務端通訊,通過while迴圈不斷去讀取伺服器傳送過來的訊息
// 接收伺服器端的訊息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!isFinishing()) {
final String msg = br.readLine();
if (msg != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_message.setText(tv_message.getText() + "\n" + "服務端:" + msg);
}
}
);
}
}
5) 客戶端在onCreate中單開執行緒啟動連線服務
Intent service = new Intent(this, SocketServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectSocketServer();
}
}.start();
6.7 程式間通訊方式比較
相關文章
- Android 程式之間通訊Android
- Android程式間通訊詳解Android
- Android程式間通訊,AIDL工作原理AndroidAI
- Android程式間通訊(複習筆記)Android筆記
- 程式間的通訊
- Flutter與android之間的通訊FlutterAndroid
- Android執行緒間通訊Android執行緒
- Android跨程式通訊Android
- Android 多程式通訊Android
- 程式間通訊的場景
- Linux 的程式間通訊:管道Linux
- PHP程式間通訊PHP
- 程式間通訊——LINUXLinux
- 程式間通訊(Socket)
- Linux程式間通訊Linux
- [Android]程式通訊Andromeda框架Android框架
- 什麼是程式間通訊?Linux程式間通訊有幾種方式?Linux
- 程式間通訊是什麼?Linux程式間通訊有幾種方式?Linux
- 微服務的程式間通訊(IPC)微服務
- 程式間的幾種通訊方式
- IPC-程式間通訊
- 程式間通訊簡介
- Linux程式間通訊-eventfdLinux
- 程式間通訊--訊息佇列佇列
- Android中活動間通訊總結Android
- 實現不同程式之間的通訊
- 程序間的通訊(訊號通訊)
- linux 程式間通訊之管道Linux
- linux 程式間通訊之FIFOLinux
- 實驗八 程式間通訊
- 程式間通訊方式有哪些?
- Linux程式之間如何通訊?Linux
- 程式間通訊如何加鎖
- Android native程式間通訊例項-binder結合共享記憶體Android記憶體
- 一篇看懂Android與Flutter之間的通訊AndroidFlutter
- Android開發之執行緒間通訊Android執行緒
- 通過 App Groups 實現程式間通訊APP
- Linux 下的程式間通訊:套接字和訊號Linux
- Linux 程式間通訊的六種機制Linux