516 lines
14 KiB
Java
516 lines
14 KiB
Java
package com.serenegiant.utils;
|
||
|
||
import android.util.Log;
|
||
|
||
import java.util.concurrent.LinkedBlockingDeque;
|
||
import java.util.concurrent.LinkedBlockingQueue;
|
||
|
||
public abstract class MessageTask implements Runnable {
|
||
// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること
|
||
private static final String TAG = MessageTask.class.getSimpleName();
|
||
|
||
public static class TaskBreak extends RuntimeException {
|
||
}
|
||
|
||
protected static final class Request {
|
||
int request;
|
||
int arg1;
|
||
int arg2;
|
||
Object obj;
|
||
int request_for_result;
|
||
Object result;
|
||
|
||
private Request() {
|
||
request = request_for_result = REQUEST_TASK_NON;
|
||
}
|
||
|
||
/**
|
||
* @param _request minus value is reserved internal use
|
||
* @param _arg1
|
||
* @param _arg2
|
||
* @param _obj
|
||
*/
|
||
public Request(final int _request, final int _arg1, final int _arg2, final Object _obj) {
|
||
request = _request;
|
||
arg1 = _arg1;
|
||
arg2 = _arg2;
|
||
obj = _obj;
|
||
request_for_result = REQUEST_TASK_NON;
|
||
}
|
||
|
||
public void setResult(final Object result) {
|
||
synchronized (this) {
|
||
this.result = result;
|
||
request = request_for_result = REQUEST_TASK_NON;
|
||
notifyAll();
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean equals(final Object o) {
|
||
return (o instanceof Request)
|
||
? (request == ((Request) o).request)
|
||
&& (request_for_result == ((Request)o).request_for_result)
|
||
&& (arg1 == ((Request) o).arg1)
|
||
&& (arg2 == ((Request) o).arg2)
|
||
&& (obj == ((Request) o).obj)
|
||
: super.equals(o);
|
||
}
|
||
}
|
||
|
||
// minus values and zero are reserved for internal use
|
||
protected static final int REQUEST_TASK_NON = 0;
|
||
protected static final int REQUEST_TASK_RUN = -1;
|
||
protected static final int REQUEST_TASK_RUN_AND_WAIT = -2;
|
||
protected static final int REQUEST_TASK_START = -8;
|
||
protected static final int REQUEST_TASK_QUIT = -9;
|
||
|
||
private final Object mSync = new Object();
|
||
/** プール/キューのサイズ, -1なら無制限 */
|
||
private final int mMaxRequest;
|
||
private final LinkedBlockingQueue<Request> mRequestPool; // FIXME これはArrayListにした方が速いかも
|
||
private final LinkedBlockingDeque<Request> mRequestQueue;
|
||
private volatile boolean mIsRunning, mFinished;
|
||
private Thread mWorkerThread;
|
||
|
||
/**
|
||
* コンストラクタ
|
||
* プール&キューのサイズは無制限
|
||
* プールは空で生成
|
||
*/
|
||
public MessageTask() {
|
||
mMaxRequest = -1;
|
||
mRequestPool = new LinkedBlockingQueue<Request>();
|
||
mRequestQueue = new LinkedBlockingDeque<Request>();
|
||
}
|
||
|
||
/**
|
||
* コンストラクタ
|
||
* プール&キューのサイズは無制限
|
||
* @param init_num プールするRequestの初期数を指定
|
||
*/
|
||
public MessageTask(final int init_num) {
|
||
mMaxRequest = -1;
|
||
mRequestPool = new LinkedBlockingQueue<Request>();
|
||
mRequestQueue = new LinkedBlockingDeque<Request>();
|
||
for (int i = 0; i < init_num; i++) {
|
||
if (!mRequestPool.offer(new Request())) break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* コンストラクタ
|
||
* プール及びキュー可能な最大サイズを指定して初期化
|
||
* @param max_request キューの最大サイズを指定
|
||
* @param init_num プールするRequestの初期数を指定, max_requestよりも大きければ切り捨てる
|
||
*/
|
||
public MessageTask(final int max_request, final int init_num) {
|
||
mMaxRequest = max_request;
|
||
mRequestPool = new LinkedBlockingQueue<Request>(max_request);
|
||
mRequestQueue = new LinkedBlockingDeque<Request>(max_request);
|
||
for (int i = 0; i < init_num; i++) {
|
||
if (!mRequestPool.offer(new Request())) break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初期化要求。継承クラスのコンストラクタから呼び出すこと
|
||
* パラメータはonInitに引き渡される
|
||
* @param arg1
|
||
* @param arg2
|
||
* @param obj
|
||
*/
|
||
protected void init(final int arg1, final int arg2, final Object obj) {
|
||
mFinished = false;
|
||
mRequestQueue.offer(obtain(REQUEST_TASK_START, arg1, arg2, obj));
|
||
// offer(REQUEST_TASK_START, arg1, arg2, obj);
|
||
}
|
||
|
||
/** 初期化処理 */
|
||
protected abstract void onInit(final int arg1, final int arg2, final Object obj);
|
||
|
||
/** 要求処理ループ開始直前に呼ばれる */
|
||
protected abstract void onStart();
|
||
|
||
/** onStopの直前に呼び出される, interruptされた時は呼び出されない */
|
||
protected void onBeforeStop() {}
|
||
|
||
/** 停止処理, interruptされた時は呼び出されない */
|
||
protected abstract void onStop();
|
||
|
||
/** onStop後に呼び出される。onStopで例外発生しても呼ばれる */
|
||
protected abstract void onRelease();
|
||
|
||
/**
|
||
* メッセージ処理ループ中でのエラー発生時の処理
|
||
* デフフォルトはtrueを返しメッセージ処理ループを終了する
|
||
* @return trueを返すとメッセージ処理ループを終了する
|
||
*/
|
||
protected boolean onError(final Exception e) {
|
||
// if (DEBUG) Log.w(TAG, e);
|
||
return true;
|
||
}
|
||
|
||
/** 要求メッセージの処理(内部メッセージは来ない)
|
||
* TaskBreakをthrowすると要求メッセージ処理ループを終了する */
|
||
protected abstract Object processRequest(final int request, final int arg1, final int arg2, final Object obj) throws TaskBreak;
|
||
|
||
/** 要求メッセージを取り出す処理(要求メッセージがなければブロックされる) */
|
||
protected Request takeRequest() throws InterruptedException {
|
||
return mRequestQueue.take();
|
||
}
|
||
|
||
public boolean waitReady() {
|
||
synchronized (mSync) {
|
||
for ( ; !mIsRunning && !mFinished ; ) {
|
||
try {
|
||
mSync.wait(500);
|
||
} catch (final InterruptedException e) {
|
||
break;
|
||
}
|
||
}
|
||
return mIsRunning;
|
||
}
|
||
}
|
||
|
||
public boolean isRunning() {
|
||
return mIsRunning;
|
||
}
|
||
|
||
public boolean isFinished() {
|
||
return mFinished;
|
||
}
|
||
|
||
@Override
|
||
public void run() {
|
||
Request request = null;
|
||
mIsRunning = true;
|
||
try {
|
||
request = mRequestQueue.take();
|
||
} catch (final InterruptedException e) {
|
||
mIsRunning = false;
|
||
mFinished = true;
|
||
}
|
||
synchronized (mSync) {
|
||
if (mIsRunning) {
|
||
mWorkerThread = Thread.currentThread();
|
||
try {
|
||
onInit(request.arg1, request.arg2, request.obj);
|
||
} catch (final Exception e) {
|
||
Log.w(TAG, e);
|
||
mIsRunning = false;
|
||
mFinished = true;
|
||
}
|
||
}
|
||
mSync.notifyAll();
|
||
}
|
||
if (mIsRunning) {
|
||
try {
|
||
onStart();
|
||
} catch (final Exception e) {
|
||
if (callOnError(e)) {
|
||
mIsRunning = false;
|
||
mFinished = true;
|
||
}
|
||
}
|
||
}
|
||
LOOP: for (; mIsRunning; ) {
|
||
try {
|
||
request = takeRequest();
|
||
switch (request.request) {
|
||
case REQUEST_TASK_NON:
|
||
break;
|
||
case REQUEST_TASK_QUIT:
|
||
break LOOP;
|
||
case REQUEST_TASK_RUN:
|
||
if (request.obj instanceof Runnable)
|
||
try {
|
||
((Runnable)request.obj).run();
|
||
} catch (final Exception e) {
|
||
if (callOnError(e))
|
||
break LOOP;
|
||
}
|
||
break;
|
||
case REQUEST_TASK_RUN_AND_WAIT:
|
||
try {
|
||
request.setResult(processRequest(request.request_for_result, request.arg1, request.arg2, request.obj));
|
||
} catch (final TaskBreak e) {
|
||
request.setResult(null);
|
||
break LOOP;
|
||
} catch (final Exception e) {
|
||
request.setResult(null);
|
||
if (callOnError(e))
|
||
break LOOP;
|
||
}
|
||
break;
|
||
default:
|
||
try {
|
||
processRequest(request.request, request.arg1, request.arg2, request.obj);
|
||
} catch (final TaskBreak e) {
|
||
break LOOP;
|
||
} catch (final Exception e) {
|
||
if (callOnError(e))
|
||
break LOOP;
|
||
}
|
||
break;
|
||
}
|
||
request.request = request.request_for_result = REQUEST_TASK_NON;
|
||
// プールへ返却する
|
||
mRequestPool.offer(request);
|
||
} catch (final InterruptedException e) {
|
||
break;
|
||
}
|
||
}
|
||
final boolean interrupted = Thread.interrupted();
|
||
synchronized (mSync) {
|
||
mWorkerThread = null;
|
||
mIsRunning = false;
|
||
mFinished = true;
|
||
}
|
||
if (!interrupted) {
|
||
try {
|
||
onBeforeStop();
|
||
onStop();
|
||
} catch (final Exception e) {
|
||
callOnError(e);
|
||
}
|
||
}
|
||
try {
|
||
onRelease();
|
||
} catch (final Exception e) {
|
||
// callOnError(e);
|
||
}
|
||
synchronized (mSync) {
|
||
mSync.notifyAll();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* エラー処理。onErrorを呼び出す。
|
||
* trueを返すと要求メッセージ処理ループを終了する
|
||
* @param e
|
||
* @return
|
||
*/
|
||
protected boolean callOnError(final Exception e) {
|
||
try {
|
||
return onError(e);
|
||
} catch (final Exception e2) {
|
||
// if (DEBUG) Log.e(TAG, "exception occurred in callOnError", e);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* RequestプールからRequestを取得する
|
||
* プールが空の場合は新規に生成する
|
||
* @param request minus values and zero are reserved
|
||
* @param arg1
|
||
* @param arg2
|
||
* @param obj
|
||
* @return Request
|
||
*/
|
||
protected Request obtain(final int request, final int arg1, final int arg2, final Object obj) {
|
||
Request req = mRequestPool.poll();
|
||
if (req != null) {
|
||
req.request = request;
|
||
req.arg1 = arg1;
|
||
req.arg2 = arg2;
|
||
req.obj = obj;
|
||
} else {
|
||
req = new Request(request, arg1, arg2, obj);
|
||
}
|
||
return req;
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread
|
||
* @param request minus values and zero are reserved
|
||
* @param arg1
|
||
* @param arg2
|
||
* @param obj
|
||
* @return true if success offer
|
||
*/
|
||
public boolean offer(final int request, final int arg1, final int arg2, final Object obj) {
|
||
return !mFinished && mRequestQueue.offer(obtain(request, arg1, arg2, obj));
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread
|
||
* @param request minus values and zero are reserved
|
||
* @param arg1
|
||
* @param obj
|
||
* @return true if success offer
|
||
*/
|
||
public boolean offer(final int request, final int arg1, final Object obj) {
|
||
return !mFinished && mRequestQueue.offer(obtain(request, arg1, 0, obj));
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread
|
||
* @param request minus values and zero are reserved
|
||
* @param arg1
|
||
* @param arg2
|
||
* @return true if success offer
|
||
*/
|
||
public boolean offer(final int request, final int arg1, final int arg2) {
|
||
return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, arg1, arg2, null));
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread
|
||
* @param request minus values and zero are reserved
|
||
* @param arg1
|
||
* @return true if success offer
|
||
*/
|
||
public boolean offer(final int request, final int arg1) {
|
||
return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, arg1, 0, null));
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread
|
||
* @param request minus values and zero are reserved
|
||
* @return true if success offer
|
||
*/
|
||
public boolean offer(final int request) {
|
||
return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, 0, 0, null));
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread
|
||
* @param request minus values and zero are reserved
|
||
* @param obj
|
||
* @return true if success offer
|
||
*/
|
||
public boolean offer(final int request, final Object obj) {
|
||
return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, 0, 0, obj));
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread on top of the request queue
|
||
* @param request minus values and zero are reserved
|
||
* @param arg1
|
||
* @param arg2
|
||
*/
|
||
public boolean offerFirst(final int request, final int arg1, final int arg2, final Object obj) {
|
||
return !mFinished && mIsRunning && mRequestQueue.offerFirst(obtain(request, arg1, arg2, obj));
|
||
}
|
||
|
||
/**
|
||
* offer request to run on worker thread and wait for result
|
||
* caller thread is blocked until the request finished running on worker thread
|
||
* FIXME このメソッドはMessageTaskを実行中のスレッド上で呼び出すとデッドロックする
|
||
* @param request
|
||
* @param arg1
|
||
* @param arg2
|
||
* @param obj
|
||
* @return
|
||
*/
|
||
public Object offerAndWait(final int request, final int arg1, final int arg2, final Object obj) {
|
||
if (!mFinished && (request > REQUEST_TASK_NON)) {
|
||
final Request req = obtain(REQUEST_TASK_RUN_AND_WAIT, arg1, arg2, obj);
|
||
synchronized (req) {
|
||
req.request_for_result = request;
|
||
req.result = null;
|
||
mRequestQueue.offer(req);
|
||
for (; mIsRunning && (req.request_for_result != REQUEST_TASK_NON); ) {
|
||
try {
|
||
req.wait(100);
|
||
} catch (final InterruptedException e) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return req.result;
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* request to run on worker thread
|
||
* @param task
|
||
* @return true if success queue
|
||
*/
|
||
public boolean queueEvent(final Runnable task) {
|
||
return !mFinished && (task != null) && offer(REQUEST_TASK_RUN, task);
|
||
}
|
||
|
||
public void removeRequest(final Request request) {
|
||
for (final Request req: mRequestQueue) {
|
||
if (!mIsRunning || mFinished) break;
|
||
if (req.equals(request)) {
|
||
mRequestQueue.remove(req);
|
||
mRequestPool.offer(req);
|
||
}
|
||
}
|
||
}
|
||
|
||
public void removeRequest(final int request) {
|
||
for (final Request req: mRequestQueue) {
|
||
if (!mIsRunning || mFinished) break;
|
||
if (req.request == request) {
|
||
mRequestQueue.remove(req);
|
||
mRequestPool.offer(req);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* request terminate worker thread and release all related resources
|
||
*/
|
||
public void release() {
|
||
release(false);
|
||
}
|
||
|
||
/**
|
||
* request terminate worker thread and release all related resources
|
||
* @param interrupt trueなら実行中のタスクをinterruptする
|
||
*/
|
||
public void release(final boolean interrupt) {
|
||
final boolean b = mIsRunning;
|
||
mIsRunning = false;
|
||
if (!mFinished) {
|
||
mRequestQueue.clear();
|
||
mRequestQueue.offerFirst(obtain(REQUEST_TASK_QUIT, 0, 0, null));
|
||
synchronized (mSync) {
|
||
if (b) {
|
||
final long current = Thread.currentThread().getId();
|
||
final long id = mWorkerThread != null ? mWorkerThread.getId() : current;
|
||
if (id != current) {
|
||
if (interrupt && (mWorkerThread != null)) {
|
||
mWorkerThread.interrupt();
|
||
}
|
||
for ( ; !mFinished ; ) {
|
||
try {
|
||
mSync.wait(300);
|
||
} catch (final InterruptedException e) {
|
||
// ignore
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 実行中のタスクが終了後開放する
|
||
*/
|
||
public void releaseSelf() {
|
||
mIsRunning = false;
|
||
if (!mFinished) {
|
||
mRequestQueue.clear();
|
||
mRequestQueue.offerFirst(obtain(REQUEST_TASK_QUIT, 0, 0, null));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* processRequest内でメッセージループを非常終了させるためのヘルパーメソッド
|
||
* 単にTaskBreakをthrowするだけ
|
||
* @throws TaskBreak
|
||
*/
|
||
public void userBreak() throws TaskBreak {
|
||
throw new TaskBreak();
|
||
}
|
||
}
|