2014-11-23 82 views
0

主要活動應啓動處理套接字通信的服務。這只是一個測試:Android:startService不會啓動目標服務

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 

    this.requestWindowFeature(Window.FEATURE_NO_TITLE); 
    this.setContentView(R.layout.contacts_test); 

    this.startService(new Intent(getApplicationContext(), WebSocketService.class)); 

    final WebSocketService service = WebSocketService.getInstance(); 

    service.runWhenConnected(new Runnable() { 
     @Override 
     public void run() { 
      service.getWebSocket().emit("login", new Credentials("...", "...")); 
     } 
    }); 

    service.registerHandler("login successful", new WebSocketEventHandler() { 
     @Override 
     public void onEvent(WebSocket webSocket, Object... args) { 
      Log.i("SOCKET-IO", "Login successful"); 
     } 
    }); 
} 

您可能會在下面找到該服務的代碼。但在開始閱讀之前,請注意控制流程甚至不會達到服務構造函數或onCreate方法;這是我無法理解的。該服務在聲明中聲明:

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
      package="org.gtd.test" 
      android:versionCode="1" 
      android:versionName="1.0"> 
    <uses-sdk android:minSdkVersion="15"/> 

    <application android:label="@string/app_name" android:icon="@drawable/tmp"> 
     <activity android:name="MyActivity" android:label="@string/app_name" android:theme="@style/LightCustomTheme"> </activity> 
     <activity android:name="PickersTestActivity" android:theme="@style/LightCustomTheme"> 
      <intent-filter> 
       <action android:name="android.intent.action.MAIN"/> 
       <category android:name="android.intent.category.LAUNCHER"/> 
      </intent-filter> 
     </activity> 
     <activity android:name=".ContactsTestActivity" android:theme="@style/LightCustomTheme"> 
      <intent-filter> 
       <action android:name="android.intent.action.MAIN"/> 
       <category android:name="android.intent.category.LAUNCHER"/> 
      </intent-filter> 
     </activity> 
     <service android:name="org.pickme.service.WebSocketService" android:enabled="true"> 
      <intent-filter> 
       <action android:name="org.pickme.service.WebSocketService" /> 
      </intent-filter> 
     </service> 
    </application> 

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> 
    <uses-permission android:name="android.permission.INTERNET" /> 
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> 
</manifest> 

我正在使用Gottox java客戶端用於socket.io。但套接字甚至沒有啓動,所以這不是問題(並且在服務之外使用它)。

package org.pickme.service; 

import android.app.Service; 
import android.content.Intent; 
import android.os.IBinder; 
import android.util.Log; 
import io.socket.IOAcknowledge; 
import io.socket.IOCallback; 
import io.socket.SocketIO; 
import io.socket.SocketIOException; 
import org.json.JSONObject; 

import java.net.MalformedURLException; 
import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 

public class WebSocketService extends Service { 
    private static final String SERVER_ENDPOINT = "http://192.168.1.83:3000"; 

    private static WebSocketService instance; 

    private SocketIO socket; 
    private WebSocket socketWrapper; 
    private boolean initialized; 

    private Thread connectionThread; 
    private IOCallback ioCallback; 

    private Runnable connectHandler, disconnectHandler; 
    private Map<String, List<WebSocketEventHandler>> eventHandlers; 


    public static WebSocketService getInstance() { 
     return instance; 
    } 

    public boolean isConnected() { 
     return socket.isConnected(); 
    } 

    public boolean isInitialized() { 
     return initialized; 
    } 

    public WebSocket getWebSocket() { 
     return socketWrapper; 
    } 


    public WebSocketService() { 
     this.ioCallback = new CallbackStub(); 
     this.eventHandlers = new HashMap<String, List<WebSocketEventHandler>>(); 
    } 


    @Override 
    public IBinder onBind(Intent intent) { 
     return null; 
    } 

    @Override 
    public void onCreate() { 
     super.onCreate(); 

     this.initialized = false; 

     try { 
      this.socket = new SocketIO(SERVER_ENDPOINT); 
      this.initialized = true; 
     } catch (MalformedURLException e) { 
      this.initialized = false; 
      return; 
     } 

     this.initWrappers(); 
     this.connectionThread.start(); 

     WebSocketService.instance = this; 
    } 

    @Override 
    public void onDestroy() { 
     super.onDestroy(); 

     this.socket.disconnect(); 
     WebSocketService.instance = null; 
    } 

    @Override 
    public int onStartCommand(Intent intent, int flags, int startId) { 
     return START_STICKY; 
    } 

    private void initWrappers() { 
     final IOCallback callbackReference = this.ioCallback; 
     final SocketIO socketReference = this.socket; 

     this.socketWrapper = new WebSocket() { 
      @Override 
      public void emit(String event, Object... args) { 
       socketReference.emit(event, args); 
      } 

      @Override 
      public void disconnect() { 
       socketReference.disconnect(); 
      } 
     }; 

     this.connectionThread = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       socketReference.connect(callbackReference); 
      } 
     }); 
    } 


    public void runOnConnect(Runnable connectHandler) { 
     this.connectHandler = connectHandler; 
    } 

    public void runOnDisconnect(Runnable disconnectHandler) { 
     this.disconnectHandler = disconnectHandler; 
    } 

    public void runWhenConnected(Runnable runnable) { 
     if (this.isConnected() && (runnable != null)) 
      runnable.run(); 
     else 
      this.runOnConnect(runnable); 
    } 

    public void registerHandler(String event, WebSocketEventHandler handler) { 
     List<WebSocketEventHandler> handlersList; 

     if (eventHandlers.containsKey(event)) 
      handlersList = eventHandlers.get(event); 
     else 
      eventHandlers.put(event, handlersList = new ArrayList<WebSocketEventHandler>()); 

     handlersList.add(handler); 
    } 

    public void unregisterHandler(String event, WebSocketEventHandler handler) { 
     if (!eventHandlers.containsKey(event)) 
      return; 

     List<WebSocketEventHandler> handlersList = eventHandlers.get(event); 
     handlersList.remove(handler); 
    } 

    public void unregisterHandlers(String event) { 
     if (!eventHandlers.containsKey(event)) 
      return; 

     List<WebSocketEventHandler> handlersList = eventHandlers.get(event); 
     eventHandlers.clear(); 

     eventHandlers.remove(event); 
    } 

    public void unregisterAllHandlers() { 
     eventHandlers.clear(); 
    } 


    private class CallbackStub implements IOCallback { 
     @Override 
     public void onDisconnect() { 
      if (WebSocketService.this.disconnectHandler != null) 
       WebSocketService.this.disconnectHandler.run(); 
     } 

     @Override 
     public void onConnect() { 
      if (WebSocketService.this.connectHandler != null) 
       WebSocketService.this.connectHandler.run(); 
     } 

     @Override 
     public void onMessage(String data, IOAcknowledge ack) { } 

     @Override 
     public void onMessage(JSONObject json, IOAcknowledge ack) { } 

     @Override 
     public void on(String event, IOAcknowledge ack, Object... args) { 
      if (!WebSocketService.this.eventHandlers.containsKey(event)) { 
       Log.i("WEBSOCKET-SERVICE", event + " unhandled"); 
       return; 
      } 

      List<WebSocketEventHandler> handlers = WebSocketService.this.eventHandlers.get(event); 

      for (WebSocketEventHandler handler : handlers) 
       handler.onEvent(WebSocketService.this.socketWrapper, args); 
     } 

     @Override 
     public void onError(SocketIOException socketIOException) { 
      Log.e("SOCKET-IO", socketIOException.getMessage(), socketIOException); 
     } 
    } 
} 

編輯:

  • 的WebSocketService 構造函數和的onCreate永遠不會調用
  • 此外,無論是否使用意圖過濾器都會發生相同的情況。
  • 我無法使用綁定,因爲即使活動終止,我也需要該服務繼續運行。
  • runWhenConnected不在主線程中調用。但是,如果這是一個例外將會提出。

回答

1

乍一看service.runWhenConnected()調用是錯誤的,因爲它在主UI線程上執行網絡代碼。不建議(我會說禁止),因爲它可能會阻止UI線程並導致onCreate不完成。我想你可能會在logcat中看到有關這種情況的錯誤消息(在UI線程上運行網絡代碼)。

ADDED1:也設置WebSocketService.instance在服務的onCreate是不正確的。 instance應該在構造函數中設置,或者靠近(例如,在getInstance()中)。當你調用

final WebSocketService service = WebSocketService.getInstance(); 

的「實例」並沒有被設定(服務的onCreate沒有被調用,但 - 這兩個活動的和服務的onCreate方法被調用相同的UI線程上,所以服務的onCreate不會被調用除非Activity的onCreate完成。startService()只是Service的onCreate的「延遲呼叫」)。所以代碼

service.runWhenConnected(...); 

最有可能拋出一個NullPointerException

一般情況下,對活動和服務使用靜態變量是一種不好的做法,它可能會導致內存泄漏。

ADDED2:如果要對付從活動爲您服務,您應該使用結合,它的標準模式中的Android這樣的情況:developer.android.com: Bound Services。不要通過靜態變量直接訪問服務的實例。改爲使用綁定

ADDED3:似乎在重新創建活動時(即在屏幕旋轉時)服務綁定可能存在問題:Communicate with Activity from Service (LocalService) - Android Best Practices。綁定也必須重新創建。這篇文章提供了相當不錯的信息和鏈接來閱讀。

ADDED4:這個帖子Bind service to FragmentActivity or Fragment?解釋片段也並不好去處約束力,但可以綁定的服務讓您應用的背景下對象,而不是,並避免與活動的再創造工作流程問題。

+0

好吧,我不知道onCreate是打電話按順序編輯。它現在有效。 – Totem 2014-11-23 20:40:36

2

我不知道你期待什麼,但此行:

this.startService(new Intent(getApplicationContext(), WebSocketService.class)); 

不是「堵」或同步調用。所以當你打電話給這個:

final WebSocketService service = WebSocketService.getInstance(); 

該服務可能沒有實例化,這可能會返回「null」。

這就是說,你的核心問題在你的清單中。您正在添加一個需要設置操作的「過濾器」。看到這個帖子:

Android Manifest- intent filter and activity

但這樣的:

<service android:name="org.pickme.service.WebSocketService" android:enabled="true"> 
     <intent-filter> 
      <action android:name="org.pickme.service.WebSocketService" /> 
     </intent-filter> 
    </service> 

你需要這樣做:

Intent myIntent = new Intent(getApplicationContext(), WebSocketService.class)); 
myIntent.setAction("org.pickme.service.WebSocketService"); 
this.startService(myIntent); 

或刪除意圖過濾器:

<service android:name="org.pickme.service.WebSocketService" android:enabled="true"> 
    </service> 
+0

我添加了意圖過濾器,因爲它之前沒有工作,並且它也不以這種方式工作。另外,onCreate甚至沒有達到,所以startService被阻塞是無關緊要的,因爲服務永遠不會啓動。 – Totem 2014-11-23 20:02:32