2010-06-16 71 views
42

我有一個活動呼叫中IDownloaderService.aidl定義的服務:Android如何等待服務實際連接?

public class Downloader extends Activity { 
IDownloaderService downloader = null; 
// ... 

在Downloader.onCreate(束)我試圖bindService

Intent serviceIntent = new Intent(this, DownloaderService.class); 
if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) { 
    // ... 

和內ServiceConnection對象SC我這樣做

public void onServiceConnected(ComponentName name, IBinder service) { 
    Log.w("XXX", "onServiceConnected"); 
    downloader = IDownloaderService.Stub.asInterface(service); 
    // ... 

通過添加各種Log.xx,我發現if(bindService(...))之後的代碼實際上是BEFORE ServiceConnection.onSer viceConnected正在被調用 - 也就是說,當下載器仍然是空的 - 這讓我陷入困境。 ApiDemos中的所有示例通過在用戶操作觸發時僅調用服務來避免此時間問題。但是我應該怎麼做才能在bindService成功後正確使用這個服務?我如何等待ServiceConnection.onServiceConnected被可靠地調用?

另一個問題有關。是否所有的事件處理程序:Activity.onCreate,任何View.onClickListener.onClick,ServiceConnection.onServiceConnected等實際上是在同一個線程中調用的(在doc中稱爲「主線程」)?它們之間是否存在交錯,或者Android會安排逐個處理所有事件?或者,什麼時候ServiceConnection.onServiceConnected實際上會被調用?完成Activity.onCreate或A.oC仍在運行時?

+0

如果你仍然需要一個答案,我給了一個解決方案在http://stackoverflow.com/a/22134635/1336747 – Alessio 2014-03-02 22:43:29

+0

@Alessio,我不認爲你的解決方案實際工作。 – Sam 2015-06-19 08:44:23

回答

47

我如何可以等待 ServiceConnection.onServiceConnected 被稱爲可靠?

你不知道。您退出onCreate()(或您綁定的任何位置),並且您在onServiceConnected()中將「需要建立連接」代碼。

是否所有的事件處理程序: Activity.onCreate,任何 View.onClickListener.onClick, ServiceConnection.onServiceConnected, 等實際調用在同一 線程

是。

究竟是什麼時候 ServiceConnection.onServiceConnected 實際上是否會被調用?在完成Activity.onCreate或 時有時候A.oC仍在運行?

你綁定請求可能甚至不打算開始直到離開onCreate()後。因此,onServiceConnected()將在您離開onCreate()之後調用。

+3

感謝您的信息。希望Android文檔能夠像這樣弄清事情。 – Ryan 2010-06-17 01:51:43

+2

各種服務API演示都有示例;具體請參閱本地服務綁定和遠程服務綁定:http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/index.html Service java doc也提供了示例本地服務綁定代碼:http://developer.android.com/reference/android/app/Service.html#LocalServiceSample – hackbod 2010-06-17 06:38:51

+0

謝謝hackbod。顯然這一部分沒有出現在我的本地文檔副本中。我會在網上看看。 – Ryan 2010-06-18 15:28:46

1

我之前做過類似的事情,唯一不同的是我沒有綁定到服務,而是剛剛啓動它。

我會通過服務廣播一個意圖來通知調用者/活動它已啓動。

+0

感謝您的快速回復。因此,在廣播接收器中,您將綁定到服務以調用RPC方法,或者在您的情況下是不是要調用它們? – Ryan 2010-06-16 17:27:07

+1

您無法綁定到來自廣播接收器的服務(因爲廣播接收是在從onReceive返回後完成的)。 – hackbod 2010-06-17 06:37:05

2

我有同樣的問題。雖然我不想將綁定的服務相關代碼放入onServiceConnected,因爲我想綁定/解除綁定onStartonStop,,但我不希望代碼在每次活動回到前面時再次運行。我只希望它在活動首次創建時運行。

我終於克服了我的onStart()隧道視野,並使用布爾值來表示這是否是第一個onServiceConnected奔跑或不。這樣,我可以在onStop中取消綁定服務,並在onStart中再次綁定服務,而無需每次運行所有啓動的東西。

2

我結束了這樣的事情:

1),提供輔助的東西一定的範圍,我創建了一個內部類。至少,醜陋的內部與其他代碼是分開的。我需要一個遠程服務做東西,因此字Something類名

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper(); 
class RemoteSomethingHelper { 
//... 
} 

2)有必要調用遠程服務方法兩樣東西:的IBinder和執行代碼。因爲我們不知道做哪一個首次成爲衆所周知,我們儲存它們:

private ISomethingService mISomethingService; 
private Runnable mActionRunnable; 

每次我們寫這些域中的一個時間,我們調用_startActionIfPossible()

private void _startActionIfPossible() { 
     if (mActionRunnable != null && mISomethingService != null) { 
      mActionRunnable.run(); 
      mActionRunnable = null; 
     } 
    } 
    private void performAction(Runnable r) { 
     mActionRunnable = r; 
     _startActionIfPossible(); 
    } 

這當然,假設Runnable可以訪問mISomethingService,但對於在RemoteSomethingHelper類的方法中創建的可運行對象來說,情況也是如此。

ServiceConnection回調are called on the UI thread確實不錯:如果我們打算從主線程調用服務方法,我們不需要關心同步。

ISomethingService當然是通過AIDL定義的。

3)而不是僅僅將參數傳遞給方法,我們創建一個Runnable將調用該方法使用這些參數後,當調用是可能的:

private boolean mServiceBound; 
    void startSomething(final String arg1) { 
     // ... starting the service ... 
     final String arg2 = ...; 
     performAction(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        // arg1 and arg2 must be final! 
        mISomethingService.startSomething(arg1, arg2); 
       } catch (RemoteException e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
    } 

4)最後,我們得到:

private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper(); 
class RemoteSomethingHelper { 
    private ISomethingService mISomethingService; 
    private Runnable mActionRunnable; 
    private boolean mServiceBound; 
    private void _startActionIfPossible() { 
     if (mActionRunnable != null && mISomethingService != null) { 
      mActionRunnable.run(); 
      mActionRunnable = null; 
     } 
    } 
    private ServiceConnection mServiceConnection = new ServiceConnection() { 
     // the methods on this class are called from the main thread of your process. 
     @Override 
     public void onServiceDisconnected(ComponentName name) { 
      mISomethingService = null; 
     } 
     @Override 
     public void onServiceConnected(ComponentName name, IBinder service) { 
      mISomethingService = ISomethingService.Stub.asInterface(service); 
      _startActionIfPossible(); 
     } 
    } 
    private void performAction(Runnable r) { 
     mActionRunnable = r; 
     _startActionIfPossible(); 
    } 

    public void startSomething(final String arg1) { 
     Intent intent = new Intent(context.getApplicationContext(),SomethingService.class); 
     if (!mServiceBound) { 
      mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0); 
     } 
     ComponentName cn = context.getApplicationContext().startService(intent); 
     final String arg2 = ...; 
     performAction(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        mISomethingService.startSomething(arg1, arg2); 
       } catch (RemoteException e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
    } 
} 

context是我班的一個領域;在活動中,您可以將其定義爲Context context=this;

我不需要排隊操作;如果你這樣做,你可以實現它。

您可能需要startSomething()中的結果回調;我做了,但是這個代碼沒有顯示。

0

*基本思想與@ 18446744073709551615一樣,但我也會分享我的代碼。

作爲主要問題的答案,

但我應該怎麼辦右使用這項服務bindService成功後?

[原創預期(但不工作)

等到連接像下面

@Override 
    protected void onStart() { 
     bindService(service, mWebServiceConnection, BIND_AUTO_CREATE); 
     synchronized (mLock) { mLock.wait(40000); } 

     // rest of the code continues here, which uses service stub interface 
     // ... 
    } 

它不會起作用,因爲在onCreate()/onStart()onServiceConnected()兩個bindService()被稱爲在服務相同的主線程onServiceConnected()在等待完成前從未被調用。

[替代溶液]

代替「等待」,定義自己的可運行到後服務關連調用和服務連接之後執行此運行的。

按如下方式實現ServiceConnection的自定義類。

public class MyServiceConnection implements ServiceConnection { 

    private static final String TAG = MyServiceConnection.class.getSimpleName(); 

    private Context mContext = null; 
    private IMyService mMyService = null; 
    private ArrayList<Runnable> runnableArrayList; 
    private Boolean isConnected = false; 

    public MyServiceConnection(Context context) { 
     mContext = context; 
     runnableArrayList = new ArrayList<>(); 
    } 

    public IMyService getInterface() { 
     return mMyService; 
    } 

    @Override 
    public void onServiceConnected(ComponentName name, IBinder service) { 
     Log.v(TAG, "Connected Service: " + name); 
     mMyService = MyService.Stub.asInterface(service); 

     isConnected = true; 
     /* Execute runnables after Service connected */ 
     for (Runnable action : runnableArrayList) { 
      action.run(); 
     } 
     runnableArrayList.clear(); 
    } 

    @Override 
    public void onServiceDisconnected(ComponentName name) { 
     try { 
      mMyService = null; 
      mContext.unbindService(this); 
      isConnected = false; 
      Log.v(TAG, "Disconnected Service: " + name); 
     } catch(Exception e) { 
      Log.e(TAG, e.toString()); 
     } 
    } 

    public void executeAfterServiceConnected(Runnable action) { 
     Log.v(TAG, "executeAfterServiceConnected"); 
     if(isConnected) { 
      Log.v(TAG, "Service already connected, execute now"); 
      action.run(); 
     } else { 
      // this action will be executed at the end of onServiceConnected method 
      Log.v(TAG, "Service not connected yet, execute later"); 
      runnableArrayList.add(action); 
     } 
    } 
} 

,然後使用它以下面的方式(在你的Activity類或等),

private MyServiceConnection myServiceConnection = null; 

@Override 
protected void onStart() { 
    Log.d(TAG, "onStart"); 
    super.onStart(); 

    Intent serviceIntent = new Intent(getApplicationContext(), MyService.class); 
    startService(serviceIntent); 
    myServiceConnection = new MyServiceConnection(getApplicationContext()); 
    bindService(serviceIntent, myServiceConnection, BIND_AUTO_CREATE); 

    // Instead of "wait" here, create callback which will be called after service is connected 
    myServiceConnection.executeAfterServiceConnected(new Runnable() { 
     @Override 
     public void run() { 
      // Rest of the code comes here. 
      // This runnable will be executed after service connected, so we can use service stub interface 
      IMyService myService = myServiceConnection.getInterface(); 
      // ... 
     } 
    }); 
} 

它爲我工作。但可能有更好的方法。

0

我發現這些解決方法只有在您的綁定服務在與應用程序的主進程不同的進程中運行時才值得努力和等待。

爲了訪問同一進程(或應用程序)中的數據和方法,我最終實現了單例類。如果這些類需要一些方法的上下文,我將應用程序上下文泄露給單例類。當然,由於它打破了「即時運行」,所以它有一個不好的後果。但我認爲這是一個更好的妥協。