2010-06-16 32 views
42

Tôi có một Hoạt động kêu gọi một dịch vụ quy định tại IDownloaderService.aidl:Android làm thế nào để tôi đợi cho đến khi một dịch vụ thực sự được kết nối?

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

Trong Downloader.onCreate (Bundle) Tôi cố gắng để bindService

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

và trong sc đối tượng ServiceConnection tôi đã làm

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

Bằng cách thêm tất cả các loại Log.xx tôi thấy rằng mã sau nếu (bindService (...)) thực sự đi TRƯỚC KHI ServiceConnection.onSer viceConnected đang được gọi - đó là, khi downloader vẫn là null - điều đó khiến tôi gặp rắc rối. Tất cả các mẫu trong ApiDemos tránh vấn đề về thời gian này chỉ bằng các dịch vụ gọi điện khi được kích hoạt bởi các hành động của người dùng. Nhưng tôi nên làm gì để sử dụng đúng dịch vụ này sau khi bindService thành công? Làm thế nào tôi có thể đợi ServiceConnection.onServiceConnected được gọi là đáng tin cậy?

Một câu hỏi khác có liên quan. Có phải tất cả các trình xử lý sự kiện: Activity.onCreate, mọi View.onClickListener.onClick, ServiceConnection.onServiceConnected, v.v. thực sự được gọi trong cùng một luồng (được đề cập trong tài liệu là "chuỗi chính")? Có xen kẽ giữa chúng hay Android sẽ lên lịch cho tất cả các sự kiện sẽ được xử lý từng cái một? Hoặc, Khi chính xác là ServiceConnection.onServiceConnected thực sự sẽ được gọi là? Sau khi hoàn thành Activity.onCreate hoặc đôi khi khi A.oC vẫn đang chạy?

+0

Nếu bạn vẫn cần câu trả lời, tôi đã đưa ra giải pháp tại http://stackoverflow.com/a/22134635/1336747 – Alessio

+0

@Alessio, tôi không nghĩ giải pháp của bạn thực sự hoạt động. – Sam

Trả lời

47

Làm cách nào để đợi ServiceConnection.onServiceConnected được gọi là đáng tin cậy?

Bạn không. Bạn thoát ra khỏi onCreate() (hoặc bất cứ nơi nào bạn đang ràng buộc) và bạn đặt bạn "cần kết nối được thiết lập" mã trong onServiceConnected().

Tất cả các xử lý sự kiện: Activity.onCreate, bất kỳ View.onClickListener.onClick, ServiceConnection.onServiceConnected, , vv thực sự gọi là trong cùng một chủ đề

Yes.

Khi nào chính xác là ServiceConnection.onServiceConnected thực sự sẽ được gọi? Khi hoàn thành hoàn thành Activity.onCreate hoặc đôi khi khi nào A.oC vẫn chạy?

Yêu cầu liên kết của bạn có thể thậm chí không đến bắt đầu cho đến khi bạn rời khỏi onCreate(). Do đó, onServiceConnected() sẽ được gọi đôi khi sau khi bạn rời khỏi onCreate().

+3

Cảm ơn thông tin này. Hy vọng các tài liệu Android có thể nhận được những điều rõ ràng như thế này. – Ryan

+2

Các bản trình diễn API dịch vụ khác nhau có các ví dụ; xem Binding dịch vụ địa phương và ràng buộc dịch vụ từ xa nói riêng: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/index.html cũng là tài liệu java dịch vụ có mẫu mã cho ràng buộc dịch vụ cục bộ: http://developer.android.com/reference/android/app/Service.html#LocalServiceSample – hackbod

+0

Cảm ơn hackbod. Rõ ràng phần này không xuất hiện trong bản sao tài liệu cục bộ của tôi. Tôi sẽ xem trực tuyến. – Ryan

1

Tôi đã làm điều gì đó tương tự trước đây, khác biệt duy nhất là tôi không ràng buộc với dịch vụ, nhưng chỉ mới bắt đầu.

Tôi sẽ phát sóng ý định từ dịch vụ để thông báo cho người gọi/hoạt động về nó được bắt đầu.

+0

Cảm ơn bạn đã trả lời nhanh. Vì vậy, trong máy thu phát, bạn sẽ liên kết với dịch vụ để gọi các phương thức RPC, hoặc bạn không gọi chúng trong trường hợp của bạn? – Ryan

+1

Bạn không thể liên kết với một dịch vụ từ một máy thu phát sóng (kể từ khi nhận được phát sóng được thực hiện một khi nó trở về từ onReceive). – hackbod

2

Tôi gặp vấn đề tương tự.Tuy nhiên, tôi không muốn đặt mã phụ thuộc dịch vụ bị ràng buộc của mình vào onServiceConnected vì tôi muốn ràng buộc/hủy liên kết với onStartonStop, nhưng tôi không muốn mã chạy lại mỗi lần hoạt động trở lại mặt trước. Tôi chỉ muốn nó chạy khi hoạt động đầu tiên được tạo ra.

Cuối cùng tôi đã vượt qua tầm nhìn đường hầm onStart() của mình và sử dụng Boolean để cho biết đây có phải là lần đầu tiên onServiceConnected chạy hay không. Bằng cách đó, tôi có thể unbindService trong onStop và bindService một lần nữa trong onStart mà không cần chạy tất cả công cụ khởi động mỗi lần.

2

Tôi đã kết thúc với một cái gì đó như thế này:

1) để cung cấp cho công cụ phụ trợ một số phạm vi, tôi đã tạo một lớp nội bộ. Ít nhất, nội bộ xấu xí được tách ra khỏi phần còn lại của mã. Tôi cần một dịch vụ từ xa làm một cái gì đó , do đó từ Something trong tên lớp

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

2) có hai điều cần thiết để gọi một phương thức dịch vụ từ xa: các IBinder và mã để thực thi. Vì chúng ta không biết cái nào trở nên nổi tiếng đầu tiên, chúng tôi lưu trữ chúng:

private ISomethingService mISomethingService; 
private Runnable mActionRunnable; 

Mỗi lần chúng tôi viết thư cho một trong những fileds, chúng tôi gọi _startActionIfPossible():

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

này, tất nhiên, giả định Runnable có quyền truy cập vào mISomethingService, nhưng điều này đúng với các runnables được tạo trong các phương thức của lớp RemoteSomethingHelper.

Thực sự tốt khi gọi lại ServiceConnection gọi lại are called on the UI thread: nếu chúng tôi định gọi các phương thức dịch vụ từ chuỗi chính, chúng tôi không cần phải quan tâm đến đồng bộ hóa.

ISomethingService, tất nhiên, được xác định qua AIDL.

3) Thay vì chỉ đi qua đối số cho phương pháp này, chúng ta tạo ra một Runnable rằng sẽ gọi phương pháp này với những lập luận sau, khi gọi có thể:

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) Cuối cùng, chúng tôi nhận được:

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 là một trường trong lớp học của tôi; trong một Hoạt động, bạn có thể xác định nó là Context context=this;

Tôi không cần phải xếp hàng; nếu bạn làm, bạn có thể thực hiện nó.

Bạn có thể sẽ cần một kết quả gọi lại trong startSomething(); Tôi đã làm, nhưng điều này không được hiển thị trong mã này.

0

* Ý tưởng cơ bản là giống với @ 18446744073709551615, nhưng tôi cũng sẽ chia sẻ mã của mình.

Như một câu trả lời của câu hỏi chính,

Nhưng những gì tôi nên làm gì để sử dụng ngay dịch vụ này sau khi bindService thành công?

[Original kỳ vọng (nhưng không làm việc)]

chờ đợi cho đến khi dịch vụ kết nối như dưới đây

@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 
     // ... 
    } 

Nó sẽ không làm việc vì cả hai bindService() trong onCreate()/onStart()onServiceConnected() được gọi tại cùng chủ đề chính. onServiceConnected() không bao giờ được gọi trước khi chờ kết thúc.

[Giải pháp thay thế]

Thay vì "chờ đợi", xác định Runnable riêng để được gọi sau khi dịch vụ được kết nối và thực hiện Runnable này sau khi dịch vụ được kết nối.

Triển khai lớp tùy chỉnh ServiceConnection như sau.

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

Và sau đó sử dụng nó theo cách sau (trong lớp Hoạt động của bạn hoặc vv),

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

Nó làm việc cho tôi. Nhưng có thể có cách tốt hơn.

0

Tôi đã tìm ra rằng các cách giải quyết này chỉ đáng được nỗ lực và chờ đợi chỉ khi các dịch vụ ràng buộc của bạn đang chạy trong một quy trình khác với quy trình chính của ứng dụng.

Để truy cập dữ liệu và phương pháp trong cùng một quy trình (hoặc ứng dụng), tôi đã kết thúc triển khai các lớp đơn. Nếu các lớp cần một ngữ cảnh cho một số phương thức, tôi đã rò rỉ ngữ cảnh ứng dụng cho các lớp singleton. Có, tất nhiên, một hậu quả xấu của nó khi nó phá vỡ "chạy ngay lập tức". Nhưng đó là một sự thỏa hiệp tổng thể tốt hơn, tôi nghĩ vậy.

Các vấn đề liên quan