1 package org.coolreader.crengine; 2 3 import java.util.ArrayList; 4 import java.util.concurrent.Callable; 5 6 import org.coolreader.crengine.ReaderView.Sync; 7 8 import android.os.Build; 9 import android.os.Handler; 10 import android.os.Looper; 11 import android.os.Message; 12 import android.util.Log; 13 14 /** 15 * Allows running tasks either in background thread or in GUI thread. 16 */ 17 public class BackgroundThread extends Thread { 18 19 private final static Object LOCK = new Object(); 20 21 private static BackgroundThread instance; 22 23 // singleton instance()24 public static BackgroundThread instance() 25 { 26 if ( instance==null ) { 27 synchronized( LOCK ) { 28 if ( instance==null ) { 29 instance = new BackgroundThread(); 30 instance.start(); 31 } 32 } 33 } 34 return instance; 35 } 36 getBackgroundHandler()37 public static Handler getBackgroundHandler() { 38 if (instance == null) 39 return null; 40 return instance.handler; 41 } 42 getGUIHandler()43 public static Handler getGUIHandler() { 44 if (instance().guiHandler == null) 45 return null; 46 return instance().guiHandler; 47 } 48 49 public final static boolean CHECK_THREAD_CONTEXT = true; 50 51 /** 52 * Throws exception if not in background thread. 53 */ ensureBackground()54 public final static void ensureBackground() 55 { 56 if ( CHECK_THREAD_CONTEXT && !isBackgroundThread() ) { 57 L.e("not in background thread", new Exception("ensureInBackgroundThread() is failed")); 58 throw new RuntimeException("ensureInBackgroundThread() is failed"); 59 } 60 } 61 62 /** 63 * Throws exception if not in GUI thread. 64 */ ensureGUI()65 public final static void ensureGUI() 66 { 67 if ( CHECK_THREAD_CONTEXT && isBackgroundThread() ) { 68 L.e("not in GUI thread", new Exception("ensureGUI() is failed")); 69 throw new RuntimeException("ensureGUI() is failed"); 70 } 71 } 72 73 // 74 private Handler handler; 75 private final ArrayList<Runnable> posted = new ArrayList<>(); 76 private Handler guiHandler; 77 private final ArrayList<Runnable> postedGUI = new ArrayList<>(); 78 79 /** 80 * Set view to post GUI tasks to. 81 * @param guiTarget is view to post GUI tasks to. 82 */ setGUIHandler(Handler guiHandler)83 public void setGUIHandler(Handler guiHandler) { 84 this.guiHandler = guiHandler; 85 if (guiHandler != null) { 86 // forward already posted events 87 synchronized(postedGUI) { 88 L.d("Engine.setGUI: " + postedGUI.size() + " posted tasks to copy"); 89 for ( Runnable task : postedGUI ) 90 guiHandler.post( task ); 91 } 92 } 93 } 94 95 /** 96 * Create background thread executor. 97 */ BackgroundThread()98 private BackgroundThread() { 99 super(); 100 setName("BackgroundThread" + Integer.toHexString(hashCode())); 101 Log.i("cr3", "Created new background thread instance"); 102 } 103 104 @Override run()105 public void run() { 106 Log.i("cr3", "Entering background thread"); 107 Looper.prepare(); 108 handler = new Handler() { 109 public void handleMessage( Message message ) 110 { 111 Log.d("cr3", "message: " + message); 112 } 113 }; 114 Log.i("cr3", "Background thread handler is created"); 115 synchronized(posted) { 116 for ( Runnable task : posted ) { 117 Log.i("cr3", "Copying posted bg task to handler : " + task); 118 handler.post(task); 119 } 120 posted.clear(); 121 } 122 Looper.loop(); 123 handler = null; 124 instance = null; 125 Log.i("cr3", "Exiting background thread"); 126 } 127 128 //private final static boolean USE_LOCK = false; guard( final Runnable r )129 private Runnable guard( final Runnable r ) 130 { 131 return r; 132 // if ( !USE_LOCK ) 133 // return r; 134 // return new Runnable() { 135 // public void run() { 136 // synchronized (LOCK) { 137 // r.run(); 138 // } 139 // } 140 // }; 141 } 142 143 /** 144 * Post runnable to be executed in background thread. 145 * @param task is runnable to execute in background thread. 146 */ postBackground( Runnable task )147 public void postBackground( Runnable task ) 148 { 149 postBackground(task, 0); 150 } 151 152 /** 153 * Post runnable to be executed in background thread. 154 * @param task is runnable to execute in background thread. 155 * @param delay is delay before running task, in millis 156 */ postBackground( Runnable task, long delay )157 public void postBackground( Runnable task, long delay ) 158 { 159 Engine.suspendLongOperation(); 160 if ( mStopped ) { 161 L.i("Posting task " + task + " to GUI queue since background thread is stopped"); 162 postGUI( task ); 163 return; 164 } 165 task = guard(task); 166 if ( handler==null ) { 167 synchronized(posted) { 168 L.i("Adding task " + task + " to posted list since handler is not yet created"); 169 posted.add(task); 170 } 171 } else { 172 if (delay > 0) { 173 Runnable finalTask = task; 174 handler.postDelayed(() -> { 175 try { 176 finalTask.run(); 177 } catch (Throwable e) { 178 Log.e("cr3", "Exception while processing task in Background thread: " + finalTask, e); 179 } 180 }, delay); 181 } else 182 handler.post(task); 183 } 184 } 185 186 /** 187 * Post runnable to be executed in GUI thread 188 * @param task is runnable to execute in GUI thread 189 */ postGUI( Runnable task )190 public void postGUI( Runnable task ) 191 { 192 postGUI(task, 0); 193 } 194 195 static int delayedTaskId = 0; 196 /** 197 * Post runnable to be executed in GUI thread 198 * @param task is runnable to execute in GUI thread 199 * @param delay is delay before running task, in millis 200 */ postGUI(final Runnable task, final long delay)201 public void postGUI(final Runnable task, final long delay) 202 { 203 try { 204 if (guiHandler == null) { 205 synchronized (postedGUI) { 206 postedGUI.add(task); 207 } 208 } else { 209 if (delay > 0) { 210 final int id = ++delayedTaskId; 211 //L.v("posting delayed (" + delay + ") task " + id + " " + task); 212 guiHandler.postDelayed(() -> { 213 try { 214 task.run(); 215 //L.v("finished delayed (" + delay + ") task " + id + " " + task); 216 } catch (Throwable e) { 217 Log.e("cr3", "Exception while processing task in GUI thread: " + task, e); 218 } 219 }, delay); 220 } else 221 guiHandler.post(task); 222 } 223 } catch (Throwable e) { 224 Log.e("cr3", "Exception while posting task to GUI thread: " + task, e); 225 } 226 } 227 228 /** 229 * Run task instantly if called from the same thread, or post it through message queue otherwise. 230 * @param task is task to execute 231 */ executeBackground( Runnable task )232 public void executeBackground( Runnable task ) 233 { 234 Engine.suspendLongOperation(); 235 task = guard(task); 236 if (isBackgroundThread() || mStopped) 237 task.run(); // run in this thread 238 else 239 postBackground(task); // post 240 } 241 242 // assume there are NOT only two threads: main GUI, this background and other thread (Synchronizer, etc) isGUIThread()243 public static boolean isGUIThread() 244 { 245 //return !isBackgroundThread(); 246 return Thread.currentThread() == Looper.getMainLooper().getThread(); 247 } 248 isBackgroundThread()249 public static boolean isBackgroundThread() 250 { 251 return (Thread.currentThread() == instance); 252 } 253 executeGUI( final Runnable task )254 public void executeGUI( final Runnable task ) 255 { 256 //Handler guiHandler = guiTarget.getHandler(); 257 //if ( guiHandler!=null && guiHandler.getLooper().getThread()==Thread.currentThread() ) 258 try { 259 if (isGUIThread()) 260 task.run(); // run in this thread 261 else { 262 postGUI(() -> { 263 try { 264 task.run(); 265 } catch (Throwable e) { 266 Log.e("cr3", 267 "Exception while executing task in GUI thread: " + task, e); 268 } 269 }); 270 } 271 } catch (Throwable e) { 272 Log.e("cr3", "Exception in executeGUI: " + task, e); 273 } 274 } 275 guard( final Callable<T> task )276 public <T> Callable<T> guard( final Callable<T> task ) 277 { 278 return new Callable<T>() { 279 public T call() throws Exception { 280 return task.call(); 281 } 282 }; 283 } 284 285 286 /** 287 * Waits until all pending background tasks are executed. 288 */ 289 public void syncWithBackground() { 290 callBackground((Callable<Integer>) () -> null); 291 } 292 293 public <T> T callBackground( final Callable<T> srcTask ) 294 { 295 final Callable<T> task = srcTask; //guard(srcTask); 296 if ( isBackgroundThread() ) { 297 try { 298 return task.call(); 299 } catch ( Exception e ) { 300 return null; 301 } 302 } 303 //L.d("executeSync called"); 304 if(DBG) L.d("callBackground : posting Background task " + Thread.currentThread().getName()); 305 final Sync<T> sync = new Sync<T>(); 306 postBackground(() -> { 307 if(DBG) L.d("callBackground : inside background thread " + Thread.currentThread().getName()); 308 try { 309 sync.set( task.call() ); 310 } catch ( Exception e ) { 311 sync.set( null ); 312 } 313 }); 314 if(DBG) L.d("callBackground : calling get " + Thread.currentThread().getName()); 315 T res = sync.get(); 316 if(DBG) L.d("callBackground : returned from get " + Thread.currentThread().getName()); 317 //L.d("executeSync done"); 318 return res; 319 } 320 321 private final static boolean DBG = false; 322 323 public <T> T callGUI( final Callable<T> task ) 324 { 325 if ( isGUIThread() ) { 326 try { 327 return task.call(); 328 } catch ( Exception e ) { 329 return null; 330 } 331 } 332 if(DBG) L.d("callGUI : posting GUI task " + Thread.currentThread().getName()); 333 final Sync<T> sync = new Sync<T>(); 334 postGUI(() -> { 335 if(DBG) L.d("callGUI : inside GUI thread " + Thread.currentThread().getName()); 336 T result = null; 337 try { 338 L.d("callGUI : calling source callable " + Thread.currentThread().getName()); 339 result = task.call(); 340 } catch ( Exception e ) { 341 if(DBG) L.e("exception in postGUI", e); 342 throw new RuntimeException(e); 343 } 344 try { 345 if(DBG) L.d("callGUI : calling sync.set " + Thread.currentThread().getName()); 346 sync.set( result ); 347 if(DBG) L.d("callGUI : returned from sync.set " + Thread.currentThread().getName()); 348 } catch ( Exception e ) { 349 if(DBG) L.e("exception in postGUI", e); 350 throw new RuntimeException(e); 351 } 352 }); 353 if(DBG) L.d("callGUI : calling get " + Thread.currentThread().getName()); 354 T res = sync.get(); 355 if(DBG) L.d("callGUI : returned from get " + Thread.currentThread().getName()); 356 return res; 357 } 358 359 private boolean mStopped = false; 360 361 public void waitForBackgroundCompletion() { 362 Engine.suspendLongOperation(); 363 callBackground(() -> null); 364 } 365 366 public void quit() { 367 postBackground(() -> { 368 if (handler != null) { 369 L.i("Calling quit() on background thread looper."); 370 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) 371 handler.getLooper().quitSafely(); 372 else 373 handler.getLooper().quit(); 374 } 375 }); 376 } 377 } 378