Frage

Ich habe eine Aktivität einen Dienst in IDownloaderService.aidl definiert Aufruf:

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

In Downloader.onCreate (Bundle) Ich bindService versucht

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

und innerhalb des ServiceConnection Objekt sc Ich habe das

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

Mit dem alle Arten von Log.xx mir Zugabe festgestellt, dass der Code nach if (bindService (...)) geht tatsächlich VOR ServiceConnection.onServiceConnected genannt wird - das heißt, wenn Downloader noch null ist - was ich in Schwierigkeiten . Alle Proben in ApiDemos vermeiden dieses Zeitproblem nur Dienste aufrufen, wenn sie von Benutzeraktionen ausgelöst. Aber was soll ich diesen Dienst zu Recht Gebrauch machen, nachdem bindService gelingt? Wie kann ich warten ServiceConnection.onServiceConnected zuverlässig aufgerufen wird?

Eine weitere Frage im Zusammenhang. Sind alle Event-Handler: Activity.onCreate, jeder View.onClickListener.onClick, ServiceConnection.onServiceConnected usw. genannt tatsächlich im selben Thread (im doc als „Haupt-Thread“ genannt)? Gibt es Verschachtelungen zwischen ihnen oder Android würden alle Ereignisse in kommen planen one-by-one behandelt werden? Oder Wann genau wird ServiceConnection.onServiceConnected geht eigentlich heißen? Nach Abschluss der Activity.onCreate oder irgendwann, wenn A.oC noch läuft?

War es hilfreich?

Lösung

  

Wie kann ich warten   ServiceConnection.onServiceConnected   wird zuverlässig?

genannt

Sie dies nicht tun. Sie verlassen aus onCreate() (oder wo auch immer Sie sind verbindlich) und Sie setzen Sie Code in onServiceConnected() „um die Verbindung hergestellt braucht“.

  

Sind die alle Event-Handler:   Activity.onCreate, jede   View.onClickListener.onClick,   ServiceConnection.onServiceConnected,   usw. tatsächlich in der gleichnamigen   Gewinde

Ja.

  

Wann genau ist   ServiceConnection.onServiceConnected   geht eigentlich heißen? Auf   Abschluss der Activity.onCreate oder   irgendwann, wenn A.oC läuft noch?

Ihre Bindungsanforderung wahrscheinlich nicht einmal geht zu starten , bis Sie onCreate() verlassen. Daher irgendwann genannt onServiceConnected() wird, nachdem Sie onCreate() verlassen.

Andere Tipps

Ich hatte das gleiche Problem. Ich wollte nicht meinen gebundenen Dienst abhängig Code in onServiceConnected setzen, obwohl, weil ich zu binden / unbind mit onStart und onStop, wollte, aber ich wollte nicht der Code wieder die Aktivität jedes Mal läuft nach vorne kam zurück. Ich wollte es erst dann laufen, wenn die Aktivität zuerst erstellt wurde.

Ich habe endlich meinen onStart() Tunnelblick über und einen Booleschen verwendet, um anzuzeigen, ob dies der erste onServiceConnected Lauf war oder nicht. Auf diese Weise kann ich unbindService in onStop und bindService wieder in onStart ohne alle die Start Sachen jedes Mal ausgeführt wird.

ich mit so etwas wie das endete:

1) die Hilfs Sachen einen gewissen Spielraum zu geben, habe ich eine interne Klasse. Zumindest werden die hässlichen Einbauten aus dem Rest des Codes getrennt. Ich brauchte einen Remote-Service zu tun etwas , daher das Wort Something in Klassennamen

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

2) gibt es zwei Dinge notwendig, um eine Remote-Service-Methode aufzurufen: die IBinder und der Code auszuführen. Da wir nicht wissen, was man zuerst bekannt wird, speichern wir sie:

private ISomethingService mISomethingService;
private Runnable mActionRunnable;

Jedes Mal, wenn wir auf eine dieser fileds schreiben, rufen wir _startActionIfPossible():

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

Dies setzt natürlich voraus, dass die Runnable Zugang zu mISomethingService hat, aber das gilt für Runnables innerhalb der Methoden der RemoteSomethingHelper Klasse erstellt.

Es ist wirklich gut, dass die ServiceConnection Rückrufe auf dem UI-Thread aufgerufen werden : wenn wir die Service-Methoden aus den Haupt-Thread gehen aufrufen, brauchen wir nicht über die Synchronisation zu kümmern

.

ISomethingService ist natürlich, definiert über AIDL.

3) Anstatt nur Argumente Methoden vorbei, wir schaffen ein Runnable, die später die Methode mit diesen Argumenten aufrufen wird, wenn Aufruf möglich:

    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) schließlich erhalten wir:

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 ist ein Feld in meiner Klasse; in einer Aktivität, können Sie es als Context context=this; definieren

Ich brauche nicht Aktionen Warteschlangen; wenn Sie das tun, können Sie es implementieren.

Sie werden wahrscheinlich ein Ergebnis Rückruf in startSomething () benötigen; Ich habe, aber dies ist nicht in diesem Code gezeigt.

Ich habe etwas ähnliches vor, die nur anders war ich nicht zu Dienstbindung, sondern nur um es zu starten.

würde ich eine Absicht aus dem Dienst überträgt den Anrufer / Aktivität zu benachrichtigen, um es gestartet wird.

* Die Grundidee ist das gleiche mit @ 18446744073709551615, aber ich werde meinen Code als auch teilen.

Als Antwort der Hauptfrage,

  

Aber was soll ich zu Recht Gebrauch machen diesen Dienst nach bindService gelingt?

[Original Erwartung (aber nicht Arbeit)]

warten, bis Dienst verbunden wie unter

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

Es wird nicht funktionieren, weil beide bindService() in onCreate()/onStart() und onServiceConnected() bei genannt wird same Hauptthread . onServiceConnected() wird nie warten beendet genannt.

[Alternative Lösung]

Anstelle von „wait“, definieren eigene Runnable aufgerufen werden, nachdem Dienst dieses runnable Connected und führen nach dem Dienst verbunden sind.

benutzerdefinierte Klasse von ServiceConnection wie folgt implementieren.

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

Und verwenden Sie es dann in der folgenden Weise (in Ihrer Aktivitätsklasse oder usw.)

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

Es funktionierte für mich. Aber es kann mehr besserer Weg geben.

Ich fand heraus, dass diese Abhilfen nur wert sind die Mühe und das Warten nur, wenn Ihre gebundenen Dienste in einem anderen Prozess ausgeführt wird als Hauptprozess Ihrer Anwendung.

Für Daten und Methoden im selben Prozess (oder Anwendung) zugreift, beenden I Singleton-Klassen Umsetzung auf. Wenn die Klassen einen Kontext für einige Methoden benötigen, ich den Anwendungskontext zu Einzelreagenten Klassen auslaufen. Es gibt natürlich eine schlechte Folge davon, wie es den „Instant-run“ bricht. Aber das ist ein insgesamt besserer Kompromiss, denke ich.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top