Example #1
0
 public RectCache(int capity) {
   mCapity = capity;
   mRectsCachePool = Pools.finitePool(manager, mCapity);
   for (int i = 0; i < mCapity; i++) {
     mRectsCachePool.release(new RectPoolableCache(new float[4]));
   }
 }
  public class CacheManager implements ICacheManager {

    @SuppressWarnings("unused")
    private static final String TAG = "CacheManager";

    public static final byte RESULT_SUCCESS = 0;
    public static final byte RESULT_FAILED = 1;
    public static final byte RESULT_FAILED_OVERSIZE = 2;

    public HandlerThread mThread;

    Danmakus mCaches = new Danmakus();

    DrawingCachePoolManager mCachePoolManager = new DrawingCachePoolManager();

    Pool<DrawingCache> mCachePool = Pools.finitePool(mCachePoolManager, 800);

    private int mMaxSize;

    private int mRealSize;

    private int mScreenSize = 3;

    private CacheHandler mHandler;

    private boolean mEndFlag;

    public CacheManager(int maxSize, int screenSize) {
      mEndFlag = false;
      mRealSize = 0;
      mMaxSize = maxSize;
      mScreenSize = screenSize;
    }

    public void seek(long mills) {
      if (mHandler == null) return;
      mHandler.requestCancelCaching();
      mHandler.removeMessages(CacheHandler.BUILD_CACHES);
      mHandler.obtainMessage(CacheHandler.SEEK, mills).sendToTarget();
    }

    @Override
    public void addDanmaku(BaseDanmaku danmaku) {
      if (mHandler != null) {
        if (danmaku.isLive) {
          if (danmaku.isTimeOut()) {
            return;
          }
          mHandler.createCache(danmaku);
        } else {
          mHandler.obtainMessage(CacheHandler.ADD_DANMAKKU, danmaku).sendToTarget();
        }
      }
    }

    public void invalidateDanmaku(BaseDanmaku danmaku) {
      if (mHandler != null && danmaku.hasDrawingCache()) {
        mHandler.obtainMessage(CacheHandler.REBUILD_CACHE, danmaku).sendToTarget();
      }
    }

    public void begin() {
      if (mThread == null) {
        mThread = new HandlerThread("DFM Cache-Building Thread");
        mThread.start();
      }
      if (mHandler == null) mHandler = new CacheHandler(mThread.getLooper());
      mHandler.begin();
    }

    public void end() {
      mEndFlag = true;
      synchronized (mDrawingNotify) {
        mDrawingNotify.notifyAll();
      }
      if (mHandler != null) {
        mHandler.pause();
        mHandler = null;
      }
      if (mThread != null) {
        try {
          mThread.join();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        mThread.quit();
        mThread = null;
      }
    }

    public void resume() {
      if (mHandler != null) {
        mHandler.resume();
      } else {
        begin();
      }
    }

    public float getPoolPercent() {
      if (mMaxSize == 0) {
        return 0;
      }
      return mRealSize / (float) mMaxSize;
    }

    public boolean isPoolFull() {
      return mRealSize + 5120 >= mMaxSize;
    }

    private void evictAll() {
      if (mCaches != null) {
        IDanmakuIterator it = mCaches.iterator();
        while (it.hasNext()) {
          BaseDanmaku danmaku = it.next();
          entryRemoved(true, danmaku, null);
        }
        mCaches.clear();
      }
      mRealSize = 0;
    }

    private void evictAllNotInScreen() {
      evictAllNotInScreen(false);
    }

    private void evictAllNotInScreen(boolean removeAllReferences) {
      if (mCaches != null) {
        IDanmakuIterator it = mCaches.iterator();
        while (it.hasNext()) {
          BaseDanmaku danmaku = it.next();
          IDrawingCache<?> cache = danmaku.cache;
          boolean hasReferences = cache != null && cache.hasReferences();
          if (removeAllReferences && hasReferences) {
            if (cache.get() != null) {
              mRealSize -= cache.size();
              cache.destroy();
            }
            entryRemoved(true, danmaku, null);
            it.remove();
            continue;
          }
          if (danmaku.hasDrawingCache() == false || danmaku.isOutside()) {
            entryRemoved(true, danmaku, null);
            it.remove();
          }
        }
        // mCaches.clear();
      }
      mRealSize = 0;
    }

    protected void entryRemoved(boolean evicted, BaseDanmaku oldValue, BaseDanmaku newValue) {
      if (oldValue.cache != null) {
        IDrawingCache<?> cache = oldValue.cache;
        long releasedSize = clearCache(oldValue);
        if (releasedSize <= 0) return;
        mRealSize -= releasedSize;
        mCachePool.release((DrawingCache) cache);
      }
    }

    private long clearCache(BaseDanmaku oldValue) {
      if (oldValue.cache.hasReferences()) {
        oldValue.cache.decreaseReference();
        oldValue.cache = null;
        return 0;
      }
      long size = sizeOf(oldValue);
      oldValue.cache.destroy();
      oldValue.cache = null;
      return size;
    }

    protected int sizeOf(BaseDanmaku value) {
      if (value.cache != null && !value.cache.hasReferences()) {
        return value.cache.size();
      }
      return 0;
    }

    private void clearCachePool() {
      DrawingCache item;
      while ((item = mCachePool.acquire()) != null) {
        item.destroy();
      }
    }

    private boolean push(BaseDanmaku item, int itemSize, boolean forcePush) {
      int size = itemSize; // sizeOf(item);
      while (mRealSize + size > mMaxSize && mCaches.size() > 0) {
        BaseDanmaku oldValue = mCaches.first();
        if (oldValue.isTimeOut()) {
          entryRemoved(false, oldValue, item);
          mCaches.removeItem(oldValue);
        } else {
          if (forcePush) {
            break;
          }
          return false;
        }
      }
      this.mCaches.addItem(item);
      mRealSize += size;
      // Log.e("CACHE", "realsize:"+mRealSize + ",size" + size);
      return true;
    }

    private void clearTimeOutCaches() {
      clearTimeOutCaches(mTimer.currMillisecond);
    }

    private void clearTimeOutCaches(long time) {
      IDanmakuIterator it = mCaches.iterator();
      while (it.hasNext() && !mEndFlag) {
        BaseDanmaku val = it.next();
        if (val.isTimeOut()) {
          synchronized (mDrawingNotify) {
            try {
              mDrawingNotify.wait(30);
            } catch (InterruptedException e) {
              e.printStackTrace();
              break;
            }
          }
          entryRemoved(false, val, null);
          it.remove();
        } else {
          break;
        }
      }
    }

    private BaseDanmaku findReuseableCache(
        BaseDanmaku refDanmaku, boolean strictMode, int maximumTimes) {
      IDanmakuIterator it = mCaches.iterator();
      int slopPixel = 0;
      if (!strictMode) {
        slopPixel = mDisp.getSlopPixel() * 2;
      }
      int count = 0;
      while (it.hasNext() && count++ < maximumTimes) { // limit maximum times
        BaseDanmaku danmaku = it.next();
        if (!danmaku.hasDrawingCache()) {
          continue;
        }
        if (danmaku.paintWidth == refDanmaku.paintWidth
            && danmaku.paintHeight == refDanmaku.paintHeight
            && danmaku.underlineColor == refDanmaku.underlineColor
            && danmaku.borderColor == refDanmaku.borderColor
            && danmaku.textColor == refDanmaku.textColor
            && danmaku.text.equals(refDanmaku.text)) {
          return danmaku;
        }
        if (strictMode) {
          continue;
        }
        if (!danmaku.isTimeOut()) {
          break;
        }
        if (danmaku.cache.hasReferences()) {
          continue;
        }
        float widthGap = danmaku.cache.width() - refDanmaku.paintWidth;
        float heightGap = danmaku.cache.height() - refDanmaku.paintHeight;
        if (widthGap >= 0 && widthGap <= slopPixel && heightGap >= 0 && heightGap <= slopPixel) {
          return danmaku;
        }
      }
      return null;
    }

    public class CacheHandler extends Handler {

      private static final int PREPARE = 0x1;

      public static final int ADD_DANMAKKU = 0x2;

      public static final int BUILD_CACHES = 0x3;

      public static final int CLEAR_TIMEOUT_CACHES = 0x4;

      public static final int SEEK = 0x5;

      public static final int QUIT = 0x6;

      public static final int CLEAR_ALL_CACHES = 0x7;

      public static final int CLEAR_OUTSIDE_CACHES = 0x8;

      public static final int CLEAR_OUTSIDE_CACHES_AND_RESET = 0x9;

      public static final int DISPATCH_ACTIONS = 0x10;

      public static final int REBUILD_CACHE = 0x11;

      private boolean mPause;

      private boolean mSeekedFlag;

      private boolean mCancelFlag;

      public CacheHandler(android.os.Looper looper) {
        super(looper);
      }

      public void requestCancelCaching() {
        mCancelFlag = true;
      }

      @Override
      public void handleMessage(Message msg) {
        int what = msg.what;
        switch (what) {
          case PREPARE:
            evictAllNotInScreen();
            for (int i = 0; i < 300; i++) {
              mCachePool.release(new DrawingCache());
            }
          case DISPATCH_ACTIONS:
            // Log.e(TAG,"dispatch_actions:"+mCacheTimer.currMillisecond+":"+mTimer.currMillisecond);
            long delayed = dispatchAction();
            if (delayed <= 0) {
              delayed = mContext.mDanmakuFactory.MAX_DANMAKU_DURATION / 2;
            }
            sendEmptyMessageDelayed(DISPATCH_ACTIONS, delayed);
            break;
          case BUILD_CACHES:
            removeMessages(BUILD_CACHES);
            boolean repositioned = ((mTaskListener != null && mReadyState == false) || mSeekedFlag);
            prepareCaches(repositioned);
            if (repositioned) mSeekedFlag = false;
            if (mTaskListener != null && mReadyState == false) {
              mTaskListener.ready();
              mReadyState = true;
            }
            //
            // Log.i(TAG,"BUILD_CACHES:"+mCacheTimer.currMillisecond+":"+mTimer.currMillisecond);
            break;
          case ADD_DANMAKKU:
            BaseDanmaku item = (BaseDanmaku) msg.obj;
            addDanmakuAndBuildCache(item);
            break;
          case REBUILD_CACHE:
            BaseDanmaku cacheitem = (BaseDanmaku) msg.obj;
            if (cacheitem.isLive) {
              clearCache(cacheitem);
              createCache(cacheitem);
            } else {
              entryRemoved(true, cacheitem, null);
              addDanmakuAndBuildCache(cacheitem);
            }
            break;
          case CLEAR_TIMEOUT_CACHES:
            clearTimeOutCaches();
            break;
          case SEEK:
            Long seekMills = (Long) msg.obj;
            if (seekMills != null) {
              mCacheTimer.update(seekMills.longValue());
              mSeekedFlag = true;
              evictAllNotInScreen();
              resume();
            }
            break;
          case QUIT:
            removeCallbacksAndMessages(null);
            mPause = true;
            evictAll();
            clearCachePool();
            this.getLooper().quit();
            break;
          case CLEAR_ALL_CACHES:
            evictAll();
            mCacheTimer.update(
                mTimer.currMillisecond - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION);
            mSeekedFlag = true;
            break;
          case CLEAR_OUTSIDE_CACHES:
            evictAllNotInScreen(true);
            mCacheTimer.update(mTimer.currMillisecond);
            break;
          case CLEAR_OUTSIDE_CACHES_AND_RESET:
            evictAllNotInScreen(true);
            mCacheTimer.update(mTimer.currMillisecond);
            requestClear();
            break;
        }
      }

      private long dispatchAction() {
        float level = getPoolPercent();
        BaseDanmaku firstCache = mCaches.first();
        // TODO 如果firstcache大于当前时间超过半屏并且水位在0.5f以下,
        long gapTime = firstCache != null ? firstCache.time - mTimer.currMillisecond : 0;
        long doubleScreenDuration = mContext.mDanmakuFactory.MAX_DANMAKU_DURATION * 2;
        if (level < 0.6f && gapTime > mContext.mDanmakuFactory.MAX_DANMAKU_DURATION) {
          mCacheTimer.update(mTimer.currMillisecond);
          removeMessages(BUILD_CACHES);
          sendEmptyMessage(BUILD_CACHES);
          return 0;
        } else if (level > 0.4f && gapTime < -doubleScreenDuration) {
          // clear timeout caches
          removeMessages(CLEAR_TIMEOUT_CACHES);
          sendEmptyMessage(CLEAR_TIMEOUT_CACHES);
          return 0;
        }

        if (level >= 0.9f) {
          return 0;
        }
        // check cache time
        long deltaTime = mCacheTimer.currMillisecond - mTimer.currMillisecond;
        if (deltaTime < 0) {
          mCacheTimer.update(mTimer.currMillisecond);
          sendEmptyMessage(CLEAR_OUTSIDE_CACHES);
          sendEmptyMessage(BUILD_CACHES);
          return 0;
        } else if (deltaTime > doubleScreenDuration) {
          return 0;
        }

        removeMessages(BUILD_CACHES);
        sendEmptyMessage(BUILD_CACHES);
        return 0;
      }

      private void releaseDanmakuCache(BaseDanmaku item, DrawingCache cache) {
        if (cache == null) {
          cache = (DrawingCache) item.cache;
        }
        item.cache = null;
        if (cache == null) {
          return;
        }
        cache.destroy();
        mCachePool.release(cache);
      }

      private long prepareCaches(boolean repositioned) {
        long curr = mCacheTimer.currMillisecond;
        long end = curr + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION * mScreenSize;
        if (end < mTimer.currMillisecond) {
          return 0;
        }
        long startTime = SystemClock.uptimeMillis();
        IDanmakus danmakus = null;
        int tryCount = 0;
        boolean hasException = false;
        do {
          try {
            danmakus = danmakuList.subnew(curr, end);
          } catch (Exception e) {
            hasException = true;
            SystemClock.sleep(10);
          }
        } while (++tryCount < 3 && danmakus == null && hasException);
        if (danmakus == null) {
          mCacheTimer.update(end);
          return 0;
        }
        BaseDanmaku first = danmakus.first();
        BaseDanmaku last = danmakus.last();
        if (first == null || last == null) {
          mCacheTimer.update(end);
          return 0;
        }
        long deltaTime = first.time - mTimer.currMillisecond;
        long sleepTime = 30 + 10 * deltaTime / mContext.mDanmakuFactory.MAX_DANMAKU_DURATION;
        sleepTime = Math.min(100, sleepTime);
        if (repositioned) {
          sleepTime = 0;
        }

        IDanmakuIterator itr = danmakus.iterator();
        BaseDanmaku item = null;
        long consumingTime = 0;
        int orderInScreen = 0;
        int currScreenIndex = 0;
        int sizeInScreen = danmakus.size();
        //                String message = "";
        while (!mPause && !mCancelFlag) {
          boolean hasNext = itr.hasNext();
          if (!hasNext) {
            //                        message = "break at not hasNext";
            break;
          }
          item = itr.next();

          if (last.time < mTimer.currMillisecond) {
            //                        message = "break at last.time < mTimer.currMillisecond";
            break;
          }

          if (item.hasDrawingCache()) {
            continue;
          }

          if (repositioned == false && (item.isTimeOut() || !item.isOutside())) {
            continue;
          }

          if (!item.hasPassedFilter()) {
            mContext.mDanmakuFilters.filter(
                item, orderInScreen, sizeInScreen, null, true, mContext);
          }

          // Log.e("prepareCache", currScreenIndex+","+orderInScreen+"," + item.time+"skip:"+skip);
          if (item.priority == 0 && item.isFiltered()) {
            continue;
          }

          if (item.getType() == BaseDanmaku.TYPE_SCROLL_RL) {
            // 同屏弹幕密度只对滚动弹幕有效
            int screenIndex =
                (int) ((item.time - curr) / mContext.mDanmakuFactory.MAX_DANMAKU_DURATION);
            if (currScreenIndex == screenIndex) orderInScreen++;
            else {
              orderInScreen = 0;
              currScreenIndex = screenIndex;
            }
          }

          if (!repositioned) {
            try {
              synchronized (mDrawingNotify) {
                mDrawingNotify.wait(sleepTime);
              }
            } catch (InterruptedException e) {
              e.printStackTrace();
              break;
            }
          }

          // build cache
          if (buildCache(item, false) == RESULT_FAILED) {
            // message = "break at build failed";
            break;
          }

          if (!repositioned) {
            consumingTime = SystemClock.uptimeMillis() - startTime;
            if (consumingTime >= DanmakuFactory.COMMON_DANMAKU_DURATION * mScreenSize) {
              //                            message = "break at consumingTime out:" + consumingTime;
              break;
            }
          }
        }
        consumingTime = SystemClock.uptimeMillis() - startTime;
        if (item != null) {
          mCacheTimer.update(item.time);
          // Log.i("cache","stop at :"+item.time+","+count+",size:"+danmakus.size()+","+message);
        } else {
          mCacheTimer.update(end);
        }
        return consumingTime;
      }

      public boolean createCache(BaseDanmaku item) {
        // measure
        if (!item.isMeasured()) {
          item.measure(mDisp);
        }
        DrawingCache cache = null;
        try {
          cache = mCachePool.acquire();
          cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache);
          item.cache = cache;
        } catch (OutOfMemoryError e) {
          // Log.e("cache", "break at error: oom");
          if (cache != null) {
            mCachePool.release(cache);
          }
          item.cache = null;
          return false;
        } catch (Exception e) {
          // Log.e("cache", "break at exception:" + e.getMessage());
          if (cache != null) {
            mCachePool.release(cache);
          }
          item.cache = null;
          return false;
        }
        return true;
      }

      private byte buildCache(BaseDanmaku item, boolean forceInsert) {

        // measure
        if (!item.isMeasured()) {
          item.measure(mDisp);
        }

        DrawingCache cache = null;
        try {
          // try to find reuseable cache
          BaseDanmaku danmaku = findReuseableCache(item, true, 20);
          if (danmaku != null) {
            cache = (DrawingCache) danmaku.cache;
          }
          if (cache != null) {
            cache.increaseReference();
            item.cache = cache;
            // Log.w("cache", danmaku.text + "DrawingCache hit!!:" + item.paintWidth + "," +
            // danmaku.paintWidth);
            mCacheManager.push(item, 0, forceInsert);
            return RESULT_SUCCESS;
          }

          // try to find reuseable cache from timeout || no-refrerence caches
          danmaku = findReuseableCache(item, false, 50);
          if (danmaku != null) {
            cache = (DrawingCache) danmaku.cache;
          }
          if (cache != null) {
            danmaku.cache = null;
            // Log.e("cache", danmaku.text + "DrawingCache hit!!:" + item.paintWidth + "," +
            // danmaku.paintWidth);
            cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache); // redraw
            item.cache = cache;
            mCacheManager.push(item, 0, forceInsert);
            return RESULT_SUCCESS;
          }

          // guess cache size
          if (!forceInsert) {
            int cacheSize =
                DanmakuUtils.getCacheSize((int) item.paintWidth, (int) item.paintHeight);
            if (mRealSize + cacheSize > mMaxSize) {
              //                        Log.d("cache", "break at MaxSize:"+mMaxSize);
              return RESULT_FAILED;
            }
          }

          cache = mCachePool.acquire();
          cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache);
          item.cache = cache;
          boolean pushed = mCacheManager.push(item, sizeOf(item), forceInsert);
          if (!pushed) {
            releaseDanmakuCache(item, cache);
            // Log.e("cache", "break at push failed:" + mMaxSize);
          }
          return pushed ? RESULT_SUCCESS : RESULT_FAILED;

        } catch (OutOfMemoryError e) {
          // Log.e("cache", "break at error: oom");
          releaseDanmakuCache(item, cache);
          return RESULT_FAILED;
        } catch (Exception e) {
          // Log.e("cache", "break at exception:" + e.getMessage());
          releaseDanmakuCache(item, cache);
          return RESULT_FAILED;
        }
      }

      private final void addDanmakuAndBuildCache(BaseDanmaku danmaku) {
        if (danmaku.isTimeOut()
            || danmaku.time
                > mCacheTimer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION) {
          return;
        }
        if (danmaku.priority == 0 && danmaku.isFiltered()) {
          return;
        }
        if (!danmaku.hasDrawingCache()) {
          buildCache(danmaku, true);
        }
        if (danmaku.isLive) {
          mCacheTimer.update(
              mTimer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION * mScreenSize);
        }
      }

      public void begin() {
        sendEmptyMessage(PREPARE);
        sendEmptyMessageDelayed(
            CLEAR_TIMEOUT_CACHES, mContext.mDanmakuFactory.MAX_DANMAKU_DURATION);
      }

      public void pause() {
        mPause = true;
        removeCallbacksAndMessages(null);
        sendEmptyMessage(QUIT);
      }

      public void resume() {
        mCancelFlag = false;
        mPause = false;
        removeMessages(DISPATCH_ACTIONS);
        sendEmptyMessage(DISPATCH_ACTIONS);
        sendEmptyMessageDelayed(
            CLEAR_TIMEOUT_CACHES, mContext.mDanmakuFactory.MAX_DANMAKU_DURATION);
      }

      public boolean isPause() {
        return mPause;
      }

      public void requestBuildCacheAndDraw(long correctionTime) {
        removeMessages(CacheHandler.BUILD_CACHES);
        mSeekedFlag = true;
        mCancelFlag = false;
        mCacheTimer.update(mTimer.currMillisecond + correctionTime);
        sendEmptyMessage(CacheHandler.BUILD_CACHES);
      }
    }

    public long getFirstCacheTime() {
      if (mCaches != null && mCaches.size() > 0) {
        BaseDanmaku firstItem = mCaches.first();
        if (firstItem == null) return 0;
        return firstItem.time;
      }
      return 0;
    }

    public void requestBuild(long correctionTime) {
      if (mHandler != null) {
        mHandler.requestBuildCacheAndDraw(correctionTime);
      }
    }

    public void requestClearAll() {
      if (mHandler == null) {
        return;
      }
      mHandler.removeMessages(CacheHandler.BUILD_CACHES);
      mHandler.requestCancelCaching();
      mHandler.removeMessages(CacheHandler.CLEAR_ALL_CACHES);
      mHandler.sendEmptyMessage(CacheHandler.CLEAR_ALL_CACHES);
    }

    public void requestClearUnused() {
      if (mHandler == null) {
        return;
      }
      mHandler.removeMessages(CacheHandler.CLEAR_OUTSIDE_CACHES_AND_RESET);
      mHandler.sendEmptyMessage(CacheHandler.CLEAR_OUTSIDE_CACHES_AND_RESET);
    }

    public void requestClearTimeout() {
      if (mHandler == null) {
        return;
      }
      mHandler.removeMessages(CacheHandler.CLEAR_TIMEOUT_CACHES);
      mHandler.sendEmptyMessage(CacheHandler.CLEAR_TIMEOUT_CACHES);
    }

    public void post(Runnable runnable) {
      if (mHandler == null) {
        return;
      }
      mHandler.post(runnable);
    }
  }