Android socket在系統休眠情況下調研
做了3年的IM應用,一直沒有確認過socket在系統休眠的情況下會不會就收不到訊息了,網上也搜過一些資料說android手機分為AP和BP兩個部分,系統休眠的時候AP是休眠的,而BP是不休眠的,網路通訊協定棧是運行在BP層的,所以當BP收到資料包的時候,系統會喚醒AP,但是AP啟動並執行時間是很短的。雖然聽起來很有道理的樣子,但是沒有親手測試過,還是一塊心病~~~,今天又想起這事,索性動手自己寫代碼測試看看結果。
Server端code:
public class TestServer { public static void main(String[] argv) { ServerSocket serverSocket; try { serverSocket = new ServerSocket(4444); Socket client; while((client = serverSocket.accept()) != null) { new ClientThread(client).start(); } } catch (IOException e) { e.printStackTrace(); } } public static class ClientThread extends Thread { private Socket socket; private OutputStream outputStream; public ClientThread(Socket client) { socket = client; try { outputStream = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } } public void run() { int index = 0; while(true) { try { outputStream.write((hello+index+).getBytes()); index++; System.out.println(send); } catch (IOException e) { e.printStackTrace(); } try { Thread.sleep(60*1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }}
代碼很簡單,Server每隔60s給client發送一句hello跟index序號。
Client端code:
public class TestActivity extends Activity { private FileOutputStream outputStream = null; private WakeLock mWakelock; private Handler handler = new Handler() { public void handleMessage(Message msg) { try { outputStream.write((new Date().toString() + ((String) msg.obj) + savelocal) .getBytes()); } catch (IOException e) { e.printStackTrace(); }// releaseWakeLock(); } }; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); new Thread(new Runnable() { @Override public void run() { File file = new File(/sdcard/testlog-lock.txt); if (file.exists()) { file.delete(); } try { file.createNewFile(); } catch (IOException e2) { e2.printStackTrace(); } try { outputStream = new FileOutputStream(file); } catch (FileNotFoundException e2) { e2.printStackTrace(); } try { Socket socket = new Socket(); socket.connect(new InetSocketAddress(10.140.82.31, 4444)); InputStream inputStream = socket.getInputStream(); BufferedReader inputStream2 = new BufferedReader(new InputStreamReader( inputStream)); String lineString; while ((lineString = inputStream2.readLine()) != null) {// acquireWakeLock(); outputStream.write((new Date().toString() + lineString + receive) .getBytes()); Message msgMessage = handler.obtainMessage(1, lineString); handler.sendMessageDelayed(msgMessage, 5000); } } catch (UnknownHostException e) { try { outputStream.write(e.getMessage().getBytes()); } catch (IOException e1) { e1.printStackTrace(); } } catch (IOException e) { try { outputStream.write(e.getMessage().getBytes()); } catch (IOException e1) { e1.printStackTrace(); } } } }).start(); } private void acquireWakeLock() { if (mWakelock == null) { PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); mWakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lock); } mWakelock.acquire(); } private void releaseWakeLock() { if (mWakelock != null && mWakelock.isHeld()) { mWakelock.release(); } mWakelock = null; }}
代碼也不複雜,Client啟動的時候會建立一個Thread去串連Server,每收到一個包就馬上往檔案裡寫入收到的內容和時間戳記,然後過5s後再次往檔案裡寫入相同的內容和時間戳記。為什麼要5s呢?因為我想驗證一下socket讀取到包之後,是不是運行一會就馬上又休眠了,如果是的,那麼5s後,第2次是不會準時寫入檔案的,因為系統休眠了,程式是不會執行的, Handler裡面的Message也就不能執行了。重要的地方是那句acquireWakelock和releaseWakelock, 如果wake了,那麼第2次寫入肯定是5s內完成。
所以我們注釋wakelock和開啟wakelock測試兩次,驗證3件事情:
1. 系統休眠後還能不能收到包,
2. 收到包之後,注釋wakelock,是什麼行為,
3. 開啟wakelock,是什麼行為。
注意測試的時候要斷開usb,因為連著usb的時候手機是不會休眠的,然後運行App,把App放後台關閉手機螢幕,分別測試半小時,看看log來驗證下猜想。
下面是測試下次的Client的log,
1. 不加wakelock
1 Mon Jul 20 22:37:16 CDT 2015hello0 receive
2 Mon Jul 20 22:37:21 CDT 2015hello0 savelocal
3 Mon Jul 20 22:38:15 CDT 2015hello1 receive
4 Mon Jul 20 22:39:15 CDT 2015hello2 receive
5 Mon Jul 20 22:40:15 CDT 2015hello3 receive
6 Mon Jul 20 22:40:15 CDT 2015hello1 savelocal
7 Mon Jul 20 22:41:15 CDT 2015hello4 receive
8 Mon Jul 20 22:42:15 CDT 2015hello5 receive
9 Mon Jul 20 22:42:15 CDT 2015hello2 savelocal
10 Mon Jul 20 22:43:15 CDT 2015hello6 receive
11 Mon Jul 20 22:43:15 CDT 2015hello3 savelocal
12 Mon Jul 20 22:44:15 CDT 2015hello7 receive
13 Mon Jul 20 22:45:15 CDT 2015hello8 receive
14 Mon Jul 20 22:46:15 CDT 2015hello4 savelocal
15 Mon Jul 20 22:47:15 CDT 2015hello10 receive
16 Mon Jul 20 22:48:15 CDT 2015hello11 receive
17 Mon Jul 20 22:48:15 CDT 2015hello5 savelocal
18 Mon Jul 20 22:49:15 CDT 2015hello12 receive
19 Mon Jul 20 22:49:15 CDT 2015hello6 savelocal
這裡只貼了部分log,可以看到資料包都以每個60s的間隔收到了,但是那個5s後save的Message代碼並沒有按照5s的頻率執行,而是等到後續的包收到之後,程式被喚醒了一下,逮到個執行空隙執行了一下。
加wakelock
1 Mon Jul 20 23:27:37 CDT 2015hello0 receive
2 Mon Jul 20 23:27:42 CDT 2015hello0 savelocal
3 Mon Jul 20 23:28:37 CDT 2015hello1 receive
4 Mon Jul 20 23:28:42 CDT 2015hello1 savelocal
5 Mon Jul 20 23:29:37 CDT 2015hello2 receive
6 Mon Jul 20 23:29:42 CDT 2015hello2 savelocal
7 Mon Jul 20 23:30:37 CDT 2015hello3 receive
8 Mon Jul 20 23:30:42 CDT 2015hello3 savelocal
9 Mon Jul 20 23:31:37 CDT 2015hello4 receive
10 Mon Jul 20 23:31:42 CDT 2015hello4 savelocal
11 Mon Jul 20 23:32:37 CDT 2015hello5 receive
12 Mon Jul 20 23:32:42 CDT 2015hello5 savelocal
13 Mon Jul 20 23:33:37 CDT 2015hello6 receive
14 Mon Jul 20 23:33:42 CDT 2015hello6 savelocal
15 Mon Jul 20 23:34:37 CDT 2015hello7 receive
可以看到save的代碼是以5s的延遲之後保證得到了運行。
OK,結論:
1. 在系統休眠的情況下,socket是能準時收到包的
2. 收到包之後,程式馬上就會再次休眠,後續想要執行一段長時間的代碼,最好是擷取一下wakelock保證這些代碼能執行到,之後釋放wakelock。這個其實很像BroadcastReceiver,系統在onReceive函數執行期間是會自動幫我們擷取wakelock的,出了這個函數就會釋放wakelock,所以如果自己想要執行一段長時間的代碼,那麼就要自己擷取跟釋放wakelock, 或者Framework裡面有提供一個叫WakefulBroadcastReceiver替我們做了這些事情。
Note:我只測試了wifi的情況下,那個BP好像只是指radio跟wifi晶片不是一個東西,不過感覺跟3g的情況下應該差不多~~~改天試試看