DecodeServiceBase.java 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. package com.poqop.document;
  2. import android.content.ContentResolver;
  3. import android.graphics.Bitmap;
  4. import android.graphics.RectF;
  5. import android.net.Uri;
  6. import android.util.Log;
  7. import android.view.View;
  8. import com.poqop.document.codec.CodecContext;
  9. import com.poqop.document.codec.CodecDocument;
  10. import com.poqop.document.codec.CodecPage;
  11. import com.poqop.document.utils.PathFromUri;
  12. import java.io.IOException;
  13. import java.lang.ref.SoftReference;
  14. import java.util.HashMap;
  15. import java.util.LinkedList;
  16. import java.util.Map;
  17. import java.util.Queue;
  18. import java.util.concurrent.*;
  19. public class DecodeServiceBase implements DecodeService
  20. {
  21. private static final int PAGE_POOL_SIZE = 16;
  22. private final CodecContext codecContext;
  23. private View containerView;
  24. private CodecDocument document;
  25. private final ExecutorService executorService = Executors.newSingleThreadExecutor();
  26. public static final String DECODE_SERVICE = "ViewDroidDecodeService";
  27. private final Map<Object, Future<?>> decodingFutures = new ConcurrentHashMap<Object, Future<?>>();
  28. private final HashMap<Integer, SoftReference<CodecPage>> pages = new HashMap<Integer, SoftReference<CodecPage>>();
  29. private ContentResolver contentResolver;
  30. private Queue<Integer> pageEvictionQueue = new LinkedList<Integer>();
  31. private boolean isRecycled;
  32. public DecodeServiceBase(CodecContext codecContext)
  33. {
  34. this.codecContext = codecContext;
  35. }
  36. public void setContentResolver(ContentResolver contentResolver)
  37. {
  38. this.contentResolver = contentResolver;
  39. codecContext.setContentResolver(contentResolver);
  40. }
  41. public void setContainerView(View containerView)
  42. {
  43. this.containerView = containerView;
  44. }
  45. public void open(Uri fileUri)
  46. {
  47. document = codecContext.openDocument(PathFromUri.retrieve(contentResolver, fileUri));
  48. }
  49. public void decodePage(Object decodeKey, int pageNum, final DecodeCallback decodeCallback, float zoom, RectF pageSliceBounds)
  50. {
  51. final DecodeTask decodeTask = new DecodeTask(pageNum, decodeCallback, zoom, decodeKey, pageSliceBounds);
  52. synchronized (decodingFutures)
  53. {
  54. if (isRecycled) {
  55. return;
  56. }
  57. final Future<?> future = executorService.submit(new Runnable()
  58. {
  59. public void run()
  60. {
  61. try
  62. {
  63. Thread.currentThread().setPriority(Thread.NORM_PRIORITY-1);
  64. performDecode(decodeTask);
  65. }
  66. catch (IOException e)
  67. {
  68. Log.e(DECODE_SERVICE, "Decode fail", e);
  69. }
  70. }
  71. });
  72. final Future<?> removed = decodingFutures.put(decodeKey, future);
  73. if (removed != null)
  74. {
  75. removed.cancel(false);
  76. }
  77. }
  78. }
  79. public void stopDecoding(Object decodeKey)
  80. {
  81. final Future<?> future = decodingFutures.remove(decodeKey);
  82. if (future != null)
  83. {
  84. future.cancel(false);
  85. }
  86. }
  87. private void performDecode(DecodeTask currentDecodeTask)
  88. throws IOException
  89. {
  90. if (isTaskDead(currentDecodeTask))
  91. {
  92. Log.d(DECODE_SERVICE, "Skipping decode task for page " + currentDecodeTask.pageNumber);
  93. return;
  94. }
  95. Log.d(DECODE_SERVICE, "Starting decode of page: " + currentDecodeTask.pageNumber);
  96. CodecPage vuPage = getPage(currentDecodeTask.pageNumber);
  97. preloadNextPage(currentDecodeTask.pageNumber);
  98. if (isTaskDead(currentDecodeTask))
  99. {
  100. return;
  101. }
  102. Log.d(DECODE_SERVICE, "Start converting map to bitmap");
  103. float scale = calculateScale(vuPage) * currentDecodeTask.zoom;
  104. final Bitmap bitmap = vuPage.renderBitmap(getScaledWidth(currentDecodeTask, vuPage, scale), getScaledHeight(currentDecodeTask, vuPage, scale), currentDecodeTask.pageSliceBounds);
  105. Log.d(DECODE_SERVICE, "Converting map to bitmap finished");
  106. if (isTaskDead(currentDecodeTask))
  107. {
  108. bitmap.recycle();
  109. return;
  110. }
  111. finishDecoding(currentDecodeTask, bitmap);
  112. }
  113. private int getScaledHeight(DecodeTask currentDecodeTask, CodecPage vuPage, float scale)
  114. {
  115. return Math.round(getScaledHeight(vuPage, scale) * currentDecodeTask.pageSliceBounds.height());
  116. }
  117. private int getScaledWidth(DecodeTask currentDecodeTask, CodecPage vuPage, float scale)
  118. {
  119. return Math.round(getScaledWidth(vuPage, scale) * currentDecodeTask.pageSliceBounds.width());
  120. }
  121. private int getScaledHeight(CodecPage vuPage, float scale)
  122. {
  123. return (int) (scale * vuPage.getHeight());
  124. }
  125. private int getScaledWidth(CodecPage vuPage, float scale)
  126. {
  127. return (int) (scale * vuPage.getWidth());
  128. }
  129. private float calculateScale(CodecPage codecPage)
  130. {
  131. return 1.0f * getTargetWidth() / codecPage.getWidth();
  132. }
  133. private void finishDecoding(DecodeTask currentDecodeTask, Bitmap bitmap)
  134. {
  135. updateImage(currentDecodeTask, bitmap);
  136. stopDecoding(currentDecodeTask.pageNumber);
  137. }
  138. private void preloadNextPage(int pageNumber) throws IOException
  139. {
  140. final int nextPage = pageNumber + 1;
  141. if (nextPage >= getPageCount())
  142. {
  143. return;
  144. }
  145. getPage(nextPage);
  146. }
  147. private CodecPage getPage(int pageIndex)
  148. {
  149. if (!pages.containsKey(pageIndex) || pages.get(pageIndex).get() == null)
  150. {
  151. pages.put(pageIndex, new SoftReference<CodecPage>(document.getPage(pageIndex)));
  152. pageEvictionQueue.remove(pageIndex);
  153. pageEvictionQueue.offer(pageIndex);
  154. if (pageEvictionQueue.size() > PAGE_POOL_SIZE) {
  155. Integer evictedPageIndex = pageEvictionQueue.poll();
  156. CodecPage evictedPage = pages.remove(evictedPageIndex).get();
  157. if (evictedPage != null) {
  158. evictedPage.recycle();
  159. }
  160. }
  161. }
  162. return pages.get(pageIndex).get();
  163. }
  164. private void waitForDecode(CodecPage vuPage)
  165. {
  166. vuPage.waitForDecode();
  167. }
  168. private int getTargetWidth()
  169. {
  170. return containerView.getWidth();
  171. }
  172. public int getEffectivePagesWidth()
  173. {
  174. final CodecPage page = getPage(0);
  175. return getScaledWidth(page, calculateScale(page));
  176. }
  177. public int getEffectivePagesHeight()
  178. {
  179. final CodecPage page = getPage(0);
  180. return getScaledHeight(page, calculateScale(page));
  181. }
  182. public int getPageWidth(int pageIndex)
  183. {
  184. return getPage(pageIndex).getWidth();
  185. }
  186. public int getPageHeight(int pageIndex)
  187. {
  188. return getPage(pageIndex).getHeight();
  189. }
  190. private void updateImage(final DecodeTask currentDecodeTask, Bitmap bitmap)
  191. {
  192. currentDecodeTask.decodeCallback.decodeComplete(bitmap);
  193. }
  194. private boolean isTaskDead(DecodeTask currentDecodeTask)
  195. {
  196. synchronized (decodingFutures)
  197. {
  198. return !decodingFutures.containsKey(currentDecodeTask.decodeKey);
  199. }
  200. }
  201. public int getPageCount()
  202. {
  203. return document.getPageCount();
  204. }
  205. private class DecodeTask
  206. {
  207. private final Object decodeKey;
  208. private final int pageNumber;
  209. private final float zoom;
  210. private final DecodeCallback decodeCallback;
  211. private final RectF pageSliceBounds;
  212. private DecodeTask(int pageNumber, DecodeCallback decodeCallback, float zoom, Object decodeKey, RectF pageSliceBounds)
  213. {
  214. this.pageNumber = pageNumber;
  215. this.decodeCallback = decodeCallback;
  216. this.zoom = zoom;
  217. this.decodeKey = decodeKey;
  218. this.pageSliceBounds = pageSliceBounds;
  219. }
  220. }
  221. public void recycle() {
  222. synchronized (decodingFutures) {
  223. isRecycled = true;
  224. }
  225. for (Object key : decodingFutures.keySet()) {
  226. stopDecoding(key);
  227. }
  228. executorService.submit(new Runnable() {
  229. public void run() {
  230. for (SoftReference<CodecPage> codecPageSoftReference : pages.values()) {
  231. CodecPage page = codecPageSoftReference.get();
  232. if (page != null) {
  233. page.recycle();
  234. }
  235. }
  236. document.recycle();
  237. codecContext.recycle();
  238. }
  239. });
  240. executorService.shutdown();
  241. }
  242. }