2014-01-21 6 views
7

Ich benutze ListenableFuture von Guava, und eine nette Sache über sie ist, dass ein Pass Executor zu der Futures.addCallback Methode, dh fragen, um den Rückruf auf einem bestimmten Thread/Executor auszuführen.Capturing Executor für aktuelle Thread

In meiner Android-Anwendung möchte ich in der Lage sein, die asynchrone Ausführung basierend auf ListenableFuture im UI-Thread zu starten, und einen Rückruf planen, der auch auf dem UI-Thread ausgeführt wird. Daher möchte ich den UI-Thread-Executor irgendwie an die oben erwähnte Methode Futures.addCallback übergeben. Wie erreiche ich das?

Oder mit anderen Worten, ich möchte einen Executor für den UI-Thread haben. Ist es bereits in Android verfügbar, oder, wenn ich mein eigenes erstellen muss, wie mache ich das?

EDIT: Als Erweiterung dieser Frage, ist es möglich, die gleiche Sache zu tun, aber nicht nur mit UI-Thread, sondern mit einem bestimmten Thread, wo der Aufruf der asynchronen Methode gemacht wird?

Ich wäre glücklich zu wissen, wie man den gleichen Effekt erreichen kann, ohne auf die Android-spezifischen Sachen wie Handler und Looper nur mit reinem Java zurückgreifen zu müssen.

+0

Konnte Ihre Frage nicht verstehen! Was ist Executer? Bitte etwas mehr ausarbeiten! Danke –

Antwort

17

Ich denke, ich habe eine Implementierung gesehen, die das macht. Die Grundidee ist grob

class UiThreadExecutor implements Executor { 
    private final Handler mHandler = new Handler(Looper.getMainLooper()); 

    @Override 
    public void execute(Runnable command) { 
     mHandler.post(command); 
    } 
} 

Sie delegieren können für den Hauptthread, indem es zu einem Handler etwas in dem Haupt-Thread ausgeführt werden.

Edit: https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit/android/MainThreadExecutor.java zum Beispiel

Edit2: Sie können den Handler wie z konfigurieren SensorManager#registerListener(..., Handler handler) können Sie tun.

class HandlerThreadExecutor implements Executor { 
    private final Handler mHandler; 
    public HandlerThreadExecutor(Handler optionalHandler) { 
     mHandler = optionalHandler != null ? optionalHandler : new Handler(Looper.getMainLooper()); 
    } 

    @Override 
    public void execute(Runnable command) { 
     mHandler.post(command); 
    } 
} 

Der Vorteil gegenüber dem aktuellen Threads Looper ist, dass es es ausdrücklich macht die Looper Sie verwenden. In deiner Lösung nimmst du den Looper von welchem ​​Thread auch immer new ExecuteOnCaller() aufruft - und das ist oft nicht der Thread, in dem du den Code später ausführst.

Ich würde gerne wissen, wie man den gleichen Effekt erzielt, ohne auf die Android-spezifischen Sachen wie Handler und Looper zurückgreifen zu müssen, nur mit reinem Java.

Looper, Handler und die Nachrichten-Warteschlange hinter all dieser Logik sind meist reine Java gemacht. Das Problem mit einer generischen Lösung ist, dass Sie Code nicht "injizieren" können, um in einem Thread zu laufen. Der Thread muss regelmäßig eine Art von Aufgabenwarteschlange überprüfen, um festzustellen, ob etwas ausgeführt werden muss.

Wenn Sie Code schreiben, wie

new Thread(new Runnable() { 
     @Override 
     public void run() { 
      while (!Thread.interrupted()) { 
       System.out.println("Hello"); 
      } 
     } 
    }).start(); 

Dann gibt es keine Möglichkeit, dass Faden sonst noch etwas zu machen, aber immer wieder drucken „Hallo“. Wenn Sie das tun könnten, wäre es, als würden Sie dynamisch einen Sprung zu anderem Code in den Programmcode einfügen. Das wäre IMO eine schreckliche Idee.

final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); 
    new Thread(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       while (true) { 
        Runnable codeToRunInThisThread = queue.take(); 
        codeToRunInThisThread.run(); 
       } 
      } catch (InterruptedException ignored) {} 
     } 
    }).start(); 

Auf der anderen Seite ist ein einfaches Gewinde, dass immer in einer Warteschlange Schleifen.Der Thread könnte dazwischen andere Aufgaben erledigen, aber Sie müssen dem Code eine manuelle Überprüfung hinzufügen.

Und Sie können es Aufgaben über

queue.put(new Runnable() { 
     @Override 
     public void run() { 
      System.out.println("Hello!"); 
     } 
    }); 

senden Es gibt keine spezielle Handler hier definiert, aber das ist der Kern dessen, was Handler & Looper in Android tun. Handler in Android können Sie einen Rückruf für eine Message anstelle von nur Runnable definieren.

Executors.newCachedThreadPool() und ähnliche tun in etwa die gleiche Sache. Es warten nur mehrere Threads auf Code in einer einzelnen Warteschlange.


Als Erweiterung auf diese Frage ist es möglich, dasselbe zu tun, aber nicht nur mit UI-Thread, aber mit einem bestimmten Thread, in dem der Aufruf von Asynchron-Verfahren hergestellt wird?

Die generische Antwort ist Nein. Nur wenn es eine Möglichkeit gibt, Code in diesen Thread zu injizieren.

+0

Was ist mit http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable)? – Fildor

+0

@Fildor das ist das gleiche, aber erfordert einen Verweis auf eine 'Activity', die Sie innerhalb dieses Executors nicht benötigen sollten. – zapl

+0

Danke, das beantwortet definitiv die Frage. Ich würde gerne mehr darüber wissen: Ist es möglich, den aktuellen Thread zu erfassen und später auszuführen, egal ob es ein UI-Thread ist oder nicht? Ich komme von C#, und ich weiß, wie es dort gemacht wird, wäre schön, dasselbe für Java zu lernen. – Haspemulator

5

Basierend auf asnwer von @zapl, hier ist meine Implementierung, die auch die bearbeitete (extended) Frage beantwortet: https://gist.github.com/RomanIakovlev/8540439

ich auch hier aus werden Figured setzen, im Falle, wenn Link eines Tages verrotten:

package com.example.concurrent; 

import android.os.Handler; 
import android.os.Looper; 

import java.util.concurrent.Executor; 

/** 
* When the calling thread has a Looper installed (like the UI thread), an instance of ExecuteOnCaller will submit 
* Runnables into the caller thread. Otherwise it will submit the Runnables to the UI thread. 
*/ 
public class ExecuteOnCaller implements Executor { 

    private static ThreadLocal<Handler> threadLocalHandler = new ThreadLocal<Handler>() { 
     @Override 
     protected Handler initialValue() { 
      Looper looper = Looper.myLooper(); 
      if (looper == null) 
      looper = Looper.getMainLooper(); 
      return new Handler(looper); 
     } 
    }; 

    private final Handler handler = threadLocalHandler.get(); 

    @Override 
    public void execute(Runnable command) { 
     handler.post(command); 
    } 
} 

Mein Muster zu verwenden, um es so sein würde:

/** 
* in SomeActivity.java or SomeFragment.java 
*/ 
Futures.addCallback(myModel.asyncOperation(param), new FutureCallback<Void>() { 
     @Override 
     public void onSuccess(Void aVoid) { 
      // handle success 
     } 

     @Override 
     public void onFailure(Throwable throwable) { 
      // handle exception 
     } 
    }, new ExecuteOnCaller()); 
3

Verwendung com.google.android.gms.tasks.TaskExecutors.MAIN_THREAD.

Ein Executor, der den Hauptanwendungs-Thread verwendet.

Quelle: Android docs

Die Aufgaben APIs sind Teil von Google since version 9.0.0 Play-Dienste.

+0

+1, obwohl dies die Nutzung der Google Play-Dienste erfordert. Auf der anderen Seite scheint es, dass diese API (Tasks) eine Menge Probleme mit "ListenableFuture" löst, insbesondere bei Aktivitätslebenszyklusproblemen (aber merkwürdigerweise nicht bei den Fragmentlebenszyklusproblemen). – Haspemulator