EditText/TextView中android-gif-drawable不刷新原因分析

問題描述

在完成SpEditTool的時(shí)候使用了android-gif-drawable一行代碼讓TextView中ImageSpan支持Gif,出現(xiàn)過兩次GifDrawable不刷新的現(xiàn)象

  • 一次是自己限制了TextView的刷新間隔寞忿,導(dǎo)致刷新頻率很快的gif刷新了TextView還沒有刷新
  • 一次是EditText刷新了但是GifDrawable沒刷新尺借,需要setLayerType(View.LAYER_TYPE_SOFTWARE, null)才起作用

原因

跑了下android-gif-drawable,發(fā)現(xiàn)上面兩個(gè)問題的原因都是同一個(gè)

先來看下代碼

class RenderTask extends SafeRunnable {

    RenderTask(GifDrawable gifDrawable) {
        super(gifDrawable);
    }

    @Override
    public void doWork() {
        final long invalidationDelay = mGifDrawable.mNativeInfoHandle.renderFrame(mGifDrawable.mBuffer);
        if (invalidationDelay >= 0) {
            mGifDrawable.mNextFrameRenderTime = SystemClock.uptimeMillis() + invalidationDelay;
            if (mGifDrawable.isVisible() && mGifDrawable.mIsRunning && !mGifDrawable.mIsRenderingTriggeredOnDraw) {
                mGifDrawable.mExecutor.remove(this);
                mGifDrawable.mRenderTaskSchedule = mGifDrawable.mExecutor.schedule(this, invalidationDelay, TimeUnit.MILLISECONDS);
            }
            if (!mGifDrawable.mListeners.isEmpty() && mGifDrawable.getCurrentFrameIndex() == mGifDrawable.mNativeInfoHandle.getNumberOfFrames() - 1) {
                mGifDrawable.mInvalidationHandler.sendEmptyMessageAtTime(mGifDrawable.getCurrentLoop(), mGifDrawable.mNextFrameRenderTime);
            }
        } else {
            mGifDrawable.mNextFrameRenderTime = Long.MIN_VALUE;
            mGifDrawable.mIsRunning = false;
        }
        if (mGifDrawable.isVisible() && !mGifDrawable.mInvalidationHandler.hasMessages(MSG_TYPE_INVALIDATION)) {
            mGifDrawable.mInvalidationHandler.sendEmptyMessageAtTime(MSG_TYPE_INVALIDATION, 0);
        }
    }
}

這個(gè)是GifDrawable用來渲染的主要代碼盈电,RenderTask的控制是交給GifDrawable的,GifDrawable在startAnimation()和draw()這兩個(gè)方法中去調(diào)度下一次渲染

    void startAnimation(long lastFrameRemainder) {
        if (mIsRenderingTriggeredOnDraw) {
            mNextFrameRenderTime = 0;
            mInvalidationHandler.sendEmptyMessageAtTime(MSG_TYPE_INVALIDATION, 0);
        } else {
            cancelPendingRenderTask();
            mRenderTaskSchedule = mExecutor.schedule(mRenderTask, Math.max(lastFrameRemainder, 0), TimeUnit.MILLISECONDS);
        }
    }
public void draw(@NonNull Canvas canvas) {
        final boolean clearColorFilter;
        if (mTintFilter != null && mPaint.getColorFilter() == null) {
            mPaint.setColorFilter(mTintFilter);
            clearColorFilter = true;
        } else {
            clearColorFilter = false;
        }
        if (mTransform == null) {
            canvas.drawBitmap(mBuffer, mSrcRect, mDstRect, mPaint);
        } else {
            mTransform.onDraw(canvas, mPaint, mBuffer);
        }
        if (clearColorFilter) {
            mPaint.setColorFilter(null);
        }

        if (mIsRenderingTriggeredOnDraw && mIsRunning && mNextFrameRenderTime != Long.MIN_VALUE) {
            final long renderDelay = Math.max(0, mNextFrameRenderTime - SystemClock.uptimeMillis());
            mNextFrameRenderTime = Long.MIN_VALUE;
            mExecutor.remove(mRenderTask);
            mRenderTaskSchedule = mExecutor.schedule(mRenderTask, renderDelay, TimeUnit.MILLISECONDS);
        }
    }

問題就出在這個(gè)draw()上面,Drawable的draw方法依賴于View或者ImageSpan等外部對(duì)象的調(diào)用蝴簇,所以View、ImageSpan如果不刷新匆帚,RenderTask就沒有了下一次渲染的機(jī)會(huì)了熬词,gif也就停了

EditText的問題

對(duì)于上面我說的不刷新的第一種情況大家好理解,TextView沒刷新嘛,GifDrawable按照上面的分析肯定也刷新不了

那EditText自己刷新了為啥也是draw()里面調(diào)度繪制的問題呢互拾,而且設(shè)置了View.LAYER_TYPE_SOFTWARE就好了呢歪今?

還是看代碼好了

GifDrawable的draw()方法是在DynamicDrawableSpan.draw()中被調(diào)用的


    @Override
    public void draw(Canvas canvas, CharSequence text,
                     int start, int end, float x, 
                     int top, int y, int bottom, Paint paint) {
        Drawable b = getCachedDrawable();
        canvas.save();
        
        int transY = bottom - b.getBounds().bottom;
        if (mVerticalAlignment == ALIGN_BASELINE) {
            transY -= paint.getFontMetricsInt().descent;
        }

        canvas.translate(x, transY);
        b.draw(canvas);
        canvas.restore();
    }
  • 順探摸瓜找到了TextLine.draw()->TextLine.drawRun()->TextLine.handleRun()->TextLine.handleReplacement()->DynamicDrawableSpan.draw()這么一條調(diào)用棧

  • TextLine.draw()方法只在Layout.draw()->Layout.drawText()中調(diào)用

  • 接下來只要找調(diào)用了Layout.draw()的地方

TextView.onDraw()方法

    @Override
    protected void onDraw(Canvas canvas) {
            ...
        if (mEditor != null) {
            mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
        } else {
            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
        }
            ..
    }
...

如果是EditText的話會(huì)調(diào)用Editor.onDraw()方法

    void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint,
            int cursorOffsetVertical) {
        ...
        if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
            drawHardwareAccelerated(canvas, layout, highlight, highlightPaint,
                    cursorOffsetVertical);
        } else {
            layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
        }
    }

從上面的代碼可以看到如果有離屏渲染且開啟了硬件加速,這是默認(rèn)的情況,渲染會(huì)走drawHardwareAccelerated(),反之view的LayerType為View.LAYER_TYPE_SOFTWARE的話會(huì)調(diào)用layout.draw()最終調(diào)用到GifDrawable.draw()

看下drawHardwareAccelerated()

    private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight,
            Paint highlightPaint, int cursorOffsetVertical) {
        ...
        } else {
            // Boring layout is used for empty and hint text
            layout.drawText(canvas, firstLine, lastLine);
        }
    }

只在EditText顯示hint的時(shí)候才會(huì)調(diào)用Layout.drawText()

結(jié)論

別看這一系列調(diào)用棧很長(zhǎng)颜矿,簡(jiǎn)單的說就是EditText默認(rèn)狀況下(離屏渲染加硬件加速)寄猩,沒有調(diào)用Layout.draw()從而導(dǎo)致GifDrawable.draw()沒走,因?yàn)镽enderTask是通過GifDrawable.draw()調(diào)度的或衡,所以gif就停止不動(dòng)了

最后

通過上面的分析我覺得這樣的將RenderTaskGifdrawable.draw()關(guān)聯(lián)的處理方式不太合理焦影,所以給作者提了issue和pr,建議在Drawable的invalidateSelf()中調(diào)度下次刷新封断,這樣gif顯示就不會(huì)依賴于外部對(duì)象了

作者接受了我的建議陌选,下個(gè)版本中應(yīng)該就不會(huì)出現(xiàn)這樣的問題了,碰到同樣問題又急著用的同志可以fork一個(gè)版本先自己改下

Fix GifDrawable invalidation. Rework of #511. Fixes #510.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末找田,一起剝皮案震驚了整個(gè)濱河市灵妨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柄瑰,老刑警劉巖闸氮,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異教沾,居然都是意外死亡蒲跨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門授翻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來或悲,“玉大人,你說我怎么就攤上這事堪唐⊙灿铮” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵淮菠,是天一觀的道長(zhǎng)男公。 經(jīng)常有香客問我,道長(zhǎng)合陵,這世上最難降的妖魔是什么枢赔? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮拥知,結(jié)果婚禮上糠爬,老公的妹妹穿的比我還像新娘。我一直安慰自己举庶,他們只是感情好执隧,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般镀琉。 火紅的嫁衣襯著肌膚如雪峦嗤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天屋摔,我揣著相機(jī)與錄音烁设,去河邊找鬼。 笑死钓试,一個(gè)胖子當(dāng)著我的面吹牛装黑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弓熏,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼恋谭,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了挽鞠?” 一聲冷哼從身側(cè)響起疚颊,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎信认,沒想到半個(gè)月后材义,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫁赏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年其掂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片潦蝇。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡款熬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出护蝶,到底是詐尸還是另有隱情华烟,我是刑警寧澤翩迈,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布持灰,位于F島的核電站,受9級(jí)特大地震影響负饲,放射性物質(zhì)發(fā)生泄漏堤魁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一返十、第九天 我趴在偏房一處隱蔽的房頂上張望妥泉。 院中可真熱鬧,春花似錦洞坑、人聲如沸盲链。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刽沾。三九已至本慕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侧漓,已是汗流浹背锅尘。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留布蔗,地道東北人藤违。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纵揍,于是被迫代替她去往敵國和親顿乒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容