Android面試收集錄14 Android進程間通信方式


 

 

一、使用 Intent

  1. Activity,Service,Receiver 都支持在 Intent 中傳遞 Bundle 數據,而 Bundle 實現了 Parcelable 接口,可以在不同的進程間進行傳輸。
  2. 在一個進程中啟動了另一個進程的 Activity,Service 和 Receiver ,可以在 Bundle 中附加要傳遞的數據通過 Intent 發送出去。

 

二、使用文件共享

  1. Windows 上,一個文件如果被加了排斥鎖會導致其他線程無法對其進行訪問,包括讀和寫;而 Android 系統基於 Linux ,使得其並發讀取文件沒有限制地進行,甚至允許兩個線程同時對一個文件進行讀寫操作,盡管這樣可能會出問題。
  2. 可以在一個進程中序列化一個對象到文件系統中,在另一個進程中反序列化恢復這個對象(注意:並不是同一個對象,只是內容相同。)。
  3. SharedPreferences 是個特例,系統對它的讀 / 寫有一定的緩存策略,即內存中會有一份 ShardPreferences 文件的緩存,系統對他的讀 / 寫就變得不可靠,當面對高並發的讀寫訪問,SharedPreferences 有很多大的幾率丟失數據。因此,IPC 不建議采用 SharedPreferences。

 

三、使用 Messenger

Messenger 是一種輕量級的 IPC 方案,它的底層實現是 AIDL ,可以在不同進程中傳遞 Message 對象,它一次只處理一個請求,在服務端不需要考慮線程同步的問題,服務端不存在並發執行的情形。

  • 服務端進程:服務端創建一個 Service 來處理客戶端請求,同時通過一個 Handler 對象來實例化一個 Messenger 對象,然后在 Service 的 onBind 中返回這個 Messenger 對象底層的 Binder 即可。
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, "我已經收到你的消息,稍后回復你!"); 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) { return mMessenger.getBinder(); } }
  • 客戶端進程:首先綁定服務端 Service ,綁定成功之后用服務端的 IBinder 對象創建一個 Messenger ,通過這個 Messenger 就可以向服務端發送消息了,消息類型是 Message 。如果需要服務端響應,則需要創建一個 Handler 並通過它來創建一個 Messenger(和服務端一樣),並通過 Message 的 replyTo 參數傳遞給服務端。服務端通過 Message 的 replyTo 參數就可以回應客戶端了。
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() { /**  * @param name  * @param service  */ @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(); } } /**  * @param name  */ @Override public void onServiceDisconnected(ComponentName name) { } }; }

**注意:**客戶端和服務端是通過拿到對方的 Messenger 來發送 Message 的。只不過客戶端通過 bindService onServiceConnected 而服務端通過 message.replyTo 來獲得對方的 Messenger 。Messenger 中有一個 Hanlder 以串行的方式處理隊列中的消息。不存在並發執行,因此我們不用考慮線程同步的問題。

 

四、使用 AIDL

Messenger 是以串行的方式處理客戶端發來的消息,如果大量消息同時發送到服務端,服務端只能一個一個處理,所以大量並發請求就不適合用 Messenger ,而且 Messenger 只適合傳遞消息,不能跨進程調用服務端的方法。AIDL 可以解決並發和跨進程調用方法的問題,要知道 Messenger 本質上也是 AIDL ,只不過系統做了封裝方便上層的調用而已。

AIDL 文件支持的數據類型

  • 基本數據類型
  • String 和 CharSequence String
  • ArrayList ,里面的元素必須能夠被 AIDL 支持;
  • HashMap ,里面的元素必須能夠被 AIDL 支持;
  • Parcelable ,實現 Parcelable 接口的對象; 注意:如果 AIDL 文件中用到了自定義的 Parcelable 對象,必須新建一個和它同名的 AIDL 文件。
  • AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。

服務端

服務端創建一個 Service 用來監聽客戶端的連接請求,然后創建一個 AIDL 文件,將暴露給客戶端的接口在這個 AIDL 文件中聲明,最后在 Service 中實現這個 AIDL 接口即可。

客戶端

綁定服務端的 Service ,綁定成功后,將服務端返回的 Binder 對象轉成 AIDL 接口所屬的類型,然后就可以調用 AIDL 中的方法了。客戶端調用遠程服務的方法,被調用的方法運行在服務端的 Binder 線程池中,同時客戶端的線程會被掛起,如果服務端方法執行比較耗時,就會導致客戶端線程長時間阻塞,導致 ANR 。客戶端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 線程中。

服務端訪問權限管理

  • 使用 Permission 驗證,在 manifest 中聲明
<permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE" android:protectionLevel="normal"/> <uses-permission android:name="com.jc.ipc.ACCESS_BOOK_SERVICE"/>

服務端 onBinder 方法中

public IBinder onBind(Intent intent) { //Permission 權限驗證 int check = checkCallingOrSelfPermission("com.jc.ipc.ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; }
  • Pid Uid 驗證

詳細代碼:

// Book.aidl package com.jc.ipc.aidl; parcelable Book;
// IBookManager.aidl package com.jc.ipc.aidl; import com.jc.ipc.aidl.Book; import com.jc.ipc.aidl.INewBookArrivedListener;  // AIDL 接口中只支持方法,不支持靜態常量,區別於傳統的接口 interface IBookManager { List<Book> getBookList(); // AIDL 中除了基本數據類型,其他數據類型必須標上方向,in,out 或者 inout // in 表示輸入型參數 // out 表示輸出型參數 // inout 表示輸入輸出型參數 void addBook(in Book book); void registerListener(INewBookArrivedListener listener); void unregisterListener(INewBookArrivedListener listener); }
// INewBookArrivedListener.aidl package com.jc.ipc.aidl; import com.jc.ipc.aidl.Book; // 提醒客戶端新書到來 interface INewBookArrivedListener { void onNewBookArrived(in Book newBook); }
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 = new ServiceConnection() { @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, "天龍八部")); } 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(); } }
public class BookManagerService extends Service { private static final String TAG = BookManagerService.class.getSimpleName(); // CopyOnWriteArrayList 支持並發讀寫,實現自動線程同步,他不是繼承自 ArrayList private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); //對象是不能跨進程傳輸的,對象的跨進程傳輸本質都是反序列化的過程,Binder 會把客戶端傳遞過來的對象重新轉化生成一個新的對象 //RemoteCallbackList 是系統專門提供的用於刪除系統跨進程 listener 的接口,利用底層的 Binder 對象是同一個 //RemoteCallbackList 會在客戶端進程終止后,自動溢出客戶端注冊的 listener ,內部自動實現了線程同步功能。 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 void onCreate() { super.onCreate(); mBookList.add(new Book(1, "老人與海")); mBookList.add(new Book(2, "哈姆雷特")); 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 void run() { 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.jc.ipc.ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } @Override public void onDestroy() { isServiceDestroied.set(true); super.onDestroy(); } }

五、使用 ContentProvider

用於不同應用間數據共享,和 Messenger 底層實現同樣是 Binder 和 AIDL,系統做了封裝,使用簡單。 系統預置了許多 ContentProvider ,如通訊錄、日程表,需要跨進程訪問。 使用方法:繼承 ContentProvider 類實現 6 個抽象方法,這六個方法均運行在 ContentProvider 進程中,除 onCreate 運行在主線程里,其他五個方法均由外界回調運行在 Binder 線程池中。

ContentProvider 的底層數據,可以是 SQLite 數據庫,可以是文件,也可以是內存中的數據。

詳見代碼:

public class BookProvider extends ContentProvider { private static final String TAG = "BookProvider"; public static final String AUTHORITY = "com.jc.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() { //不建議在 UI 線程中執行耗時操作
        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,'Html5');"); mDB.execSQL("insert into user values(1,'haohao',1);"); mDB.execSQL("insert into user values(2,'nannan',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); // 通知外界 ContentProvider 中的數據發生變化
        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; } return tableName; } }

 

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) { } }

 

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() { return super.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", "三國演義"); 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", "三國演義新版"); 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()); } }

 


 
         

六、使用 Socket

 
         

Socket起源於 Unix,而 Unix 基本哲學之一就是“一切皆文件”,都可以用“打開 open –讀寫 write/read –關閉 close ”模式來操作。Socket 就是該模式的一個實現,網絡的 Socket 數據傳輸是一種特殊的 I/O,Socket 也是一種文件描述符。Socket 也具有一個類似於打開文件的函數調用: Socket(),該函數返回一個整型的Socket 描述符,隨后的連接建立、數據傳輸等操作都是通過該 Socket 實現的。

常用的 Socket 類型有兩種:流式 Socket(SOCK_STREAM)和數據報式 Socket(SOCK_DGRAM)。流式是一種面向連接的 Socket,針對於面向連接的 TCP 服務應用;數據報式 Socket 是一種無連接的 Socket ,對應於無連接的 UDP 服務應用。

 
         

Socket 本身可以傳輸任意字節流。

 
         

談到Socket,就必須要說一說 TCP/IP 五層網絡模型:

 
         
  • 應用層:規定應用程序的數據格式,主要的協議 HTTP,FTP,WebSocket,POP3 等;
  • 傳輸層:建立“端口到端口” 的通信,主要的協議:TCP,UDP;
  • 網絡層:建立”主機到主機”的通信,主要的協議:IP,ARP ,IP 協議的主要作用:一個是為每一台計算機分配 IP 地址,另一個是確定哪些地址在同一子網;
  • 數據鏈路層:確定電信號的分組方式,主要的協議:以太網協議;
  • 物理層:負責電信號的傳輸。
 
         

Socket 是連接應用層與傳輸層之間接口(API)。

 
         

網絡模型

 
         

只實現 TCP Socket 。

 
         

Client 端代碼:

 
         
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(); } }
 
         

Server端代碼:

public class TCPServerService extends Service { private static final String TAG = "TCPServerService"; private boolean isServiceDestroyed = false; private String[] mMessages = new String[]{ "Hello! Body!", "用戶不在線!請稍后再聯系!", "請問你叫什么名字呀?", "厲害了,我的哥!", "Google 不需要科學上網是真的嗎?", "扎心了,老鐵!!!" }; @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("歡迎來到聊天室!"); 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(); } }

UDP Socket 可以自己嘗試着實現。

 

七、參考文章

  https://github.com/LRH1993/android_interview/blob/master/android/basis/ipc.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM