2012-05-09 72 views
0

新的Android程序員在這裏。我有一個執行套接字管理和異步I/O的服務,我需要在它和應用程序中的活動之間建立一條通信路徑。Android中的活動和服務之間的通信方法

當前的方法是爲Service和Activity配備BroadcastReceivers並使用它們將活動的'命令'意圖發送到服務,並將「提醒」意圖從服務發送到活動。

我的服務有一個runnable,這是socket read()發生的地方;當接收到的數據的,可運行發送「輸入數據的意圖的服務,誰再提醒活動:

@Override 
     public int onStartCommand(Intent intent, int flags, int startId) { 
      super.onStartCommand(intent, flags, startId); 
      if (m_IsRunning == false) { 
       m_IsRunning = true; 
       (new Thread(new Runnable() { 
        byte[] inputBuffer = new byte[512]; 
        public void run() { 
         while (m_IsRunning) { 
          if (m_IsConnected) { 
           try { 
            m_Nis = m_Socket.getInputStream(); 
            m_Nis.read(inputBuffer, 0, 512); 
            Intent broadcast = new Intent(); 
            Bundle bun = new Bundle(); 
            bun.putString("ServiceCmd", "ALERT_INCOMING_DATA"); 
            bun.putByteArray("MsgBuffer", inputBuffer); 
            broadcast.putExtras(bun); 
            broadcast.setAction(BROADCAST_TO_SERVICE); 
            sendBroadcast(broadcast); 
           } catch (IOException e) { 
            // Send fault to activity 
           } 
          } 
         } 
        } 
       })).start(); 
      } 
      return START_STICKY; 
     } 

我與廣播接收器的做法是這樣的:

 private BroadcastReceiver serviceReceiver = new BroadcastReceiver() { 
      @Override 
      public void onReceive(Context context, Intent intent) { 
       Bundle bun = intent.getExtras(); 
       String cmdString = bun.getString("ServiceCmd"); 

       if (cmdString.equals("CMD_SETHOSTINFO")) { 
        // The activity has requested us to set the host info 
        String hostAddr = bun.getString("HostAddressString"); 
        int hostPort = bun.getInt("HostPortNumber"); 
        processSetHostInfoCommand(hostAddr, hostPort); 
       } 
       else if (cmdString.equals("CMD_CONNECT")) { 
        // The activity has requested us to connect 
        if ((m_IsRunning) && (m_IsConnected == false)) { 
         // Attempt to connect 
         processConnectCommand(); 
        } 
       } 
       else if (cmdString.equals("CMD_DISCONNECT")) { 
        // The activity has requested us to disconnect 
        if ((m_IsRunning) && (m_IsConnected == true)) { 
         // Attempt to disconnect 
         processDisconnectCommand(); 
        } 
       } 
       else if (cmdString.equals("CMD_SENDDATA")) { 
        // The activity has requested us to send data 
        if ((m_IsRunning) && (m_IsConnected == true)) { 
         // Attempt to send data 
         byte[] msgBuffer = bun.getByteArray("MsgBuffer"); 
         processSendDataCommand(msgBuffer); 
        } 
       } 
       else if (cmdString.equals("ALERT_INCOMING_DATA")) { 
        // Our TCP receiver thread has received data 
        if (m_IsRunning) { 
         byte[] msgBuffer = bun.getByteArray("MsgBuffer"); 
         processIncomingDataAlert(msgBuffer); 
        } 
       } 
      } 
     }; 

(那些processWhatever()方法一般做套接字管理和數據傳輸。)

就像我說的,它似乎工作正常,但我想知道這是不是使用消息和處理程序不會更合適的情況。

因此,具體問題是:

  1. 什麼是「Android的陶」在決定何時使用廣播接收器/意圖或處理器/信息?

  2. 當決定使用哪種方法時,是否存在跨線程考慮因素?

(而且,雖然這是題外話,最後一個問題):

  1. 適合做那種基於套接字的我的服務/ O我想要做什麼?

回答

4

的陶廣播意圖,意圖和處理

廣播意圖是一個一對多的發佈/訂閱的情況,其中一個組件希望讓世界知道發生了什麼事情,但不關心是否有人/多少聽衆有,或者他們是否正在運行。

定期意圖是一對一情景,其中一個組件需要代表其完成特定處理,但不關心/知道是否有特定的組件能夠這樣做,或者此組件是否正在運行。在另一方面

處理程序是一個對一個同步/異步場景中雙方都是衆所周知的,當前正在運行。

上面如何與您的情況

在最簡單的實現,你不需要意圖或處理程序,您可以直接調用方法對您的服務從後臺運行的,就像這樣:

MyService.this.processIncomingDataAlert(inputBuffer); 

注意,這將執行在後臺線程的方法,並且將阻止套接字收聽器,而處理數據。這讓我們瞭解了你所要求的線程考慮。

如果你想解鎖插座監聽器,和/或處理該UI線程上的數據,你可以在onStartCommand()創建一個處理程序,並用它從可運行後的另一個可運行回到UI線程,這樣:

myServiceHandler.post(new Runnable() { 
    public void run() { 
     MyService.this.processIncomingDataAlert(inputBuffer); 
    } 
}; 

注意,這將導致競爭條件當UI線程處理到onIncomingData了Runnable呼叫,這可能會更新inputBufffer後臺線程監聽器之間,所以你可能會想創建一個副本。

當然,還有現在也該處理髮生在UI線程,這是在服務和活動之間的共享上的問題。所以,如果你的數據處理速度很慢,它會影響你的用戶界面的響應速度。

如果你想確保兩個背景套接字監聽-and_ UI線程響應,你應該處理上(還)另一個線程數據。您可以爲每個Runnable啓動一個新線程,但這會導致大量線程快速創建,並浪費系統資源。更不用說,創建單獨的線程會導致處理多個數據塊之間的競爭條件,這會使您的應用程序邏輯過於複雜。

幸運的是,Android的這種場景提供AsyncTask

AsyncTask有一個後臺線程池,它在其中運行通過它調度的Runnables。如果您在GingerBread或更低版本上運行,或者在ICS或更高版本上運行,AsyncTask也會將它們序列化並一次只運行一項任務。

閱讀this article對線程,處理器和的AsyncTask很好的介紹。請注意,本文展示了一個例子,其中描述了AsyncTask的子類並實現了doOnBackground,但這對你來說有點矯枉過正;靜態的execute(Runnable)方法將適用於你。

是我們的服務適合您的方案

這個問題有些正交的休息。你需要一個後臺線程來監聽套接字,這是給定的。但你需要一個服務是不是很清楚。

服務是其中一個應用程序需要做後臺處理,並不需要通過用戶界面來吸引該用戶,或繼續進行處理後,用戶切換到另一個應用場景。

因此,如果您的情況需要你只能同時顯示在屏幕上的活動和用戶正在積極地與它配合的插座上聽,你並不需要的服務。您可以從您的活動中啓動後臺線程,並使用處理程序將新數據回發給它或AsyncTask,如上所述。

然而,如果你確實需要繼續監聽套接字上(你是否應該是一個獨立的主題:-))用戶已經關閉後您的活動,你所需要的服務。

這裏的主要問題是Android中的流程生命週期。爲了正確管理設備資源,操作系統將終止它認爲處於空閒狀態的進程。如果沒有任何活動或服務在其中運行,則認爲該進程處於空閒狀態。僅僅啓動後臺線程不足以讓操作系統知道進程仍然很忙。所以,如果你沒有服務,一旦你的活動關閉,從Android的角度來看,你的過程什麼都不做,它可以殺死它。

希望這會有所幫助。

+0

Franci - 很多很多謝謝 - 你的文章不僅解決了我的問題,而且還回答了其他幾個未經詢問)的問題! – Literata

1

您並不需要使用服務。如果它們都在同一個進程中運行,那麼只需將網絡代碼設置爲單例,並讓活動直接調用方法即可。對於通知,你可以簡單地讓活動實現一個特定的接口(onData(String data)等),並讓他們註冊到網絡類。當活動消失時請注意取消註冊(onStop())。另一方面,如果網絡代碼需要在沒有UI可見的情況下運行(例如,不同的應用程序位於前臺),則需要使用服務。

消息和意圖實際上是不同的:您使用handlers.messages與特定線程進行通信,而意圖實際上是IPC - 它們可以被傳遞到不同的應用程序/進程。使用廣播接收器的好處是,Android將創建一個流程來處理傳入的意圖,如果目前沒有運行。

不知道這是否是「道」,但一般:

  • 如果你需要無UI運行的東西,使用服務(調度網絡IO等。)
  • 如果UI存在,只是它在不同的THEAD運行(AscynTask提供了一個方便的方式來做到這一點)
  • ,如果你與你自己的應用程序通信(在一個單一的過程中),使用處理器/消息
  • ,如果你需要跨應用程序(進程)通信,使用意圖
  • 如果你需要處理一些異步事件,並有機會應用程序來處理它不會被運行,使用廣播接收器。然後,如果需要某個時間,則可能需要啓動服務(IntentService在工作線程中進行處理)來執行實際工作。此外,您可能需要獲得一個激活鎖定,以保持設備再次入睡,而你正在執行你的工作(網絡IO等)
+0

'Android將創建一個處理傳入意圖的過程,如果一個目前沒有運行' - 我有一個預感,你做了一個區分,這對我的理解很重要;非常感謝! (順便說一句,沒有什麼,我看到了你的博客網站 - 很多有用的加密信息!) – Literata

相關問題