在我的Android应用程序中,我用a SurfaceView
来绘制东西.它在数千台设备上运行良好 - 除了现在用户开始在以下设备上报告ANR:
LG G4
Android 5.1
3 GB RAM
5.5"显示
2560 x 1440 px分辨率
索尼Xperia Z4
Android 5.0
3 GB RAM
5,2"显示
1920 x 1080 px分辨率
华为Ascend Mate 7
Android 5.1
3 GB RAM
6.0"显示
1920 x 1080 px分辨率
HTC M9
Android 5.1
3 GB RAM
5.0"显示
1920 x 1080 px分辨率
所以我得到了LG G4,确实能够验证问题.它与...直接相关SurfaceView
.
现在猜猜经过几个小时的调试后修复了什么问题?它正在取代......
mSurfaceHolder.unlockCanvasAndPost(c);
...... ......
mSurfaceHolder.unlockCanvasAndPost(c); System.out.println("123"); // THIS IS THE FIX
怎么会这样?
以下代码是我的渲染线程,除了提到的设备之外一直工作正常:
import android.graphics.Canvas; import android.view.SurfaceHolder; public class MyThread extends Thread { private final SurfaceHolder mSurfaceHolder; private final MySurfaceView mSurface; private volatile boolean mRunning = false; public MyThread(SurfaceHolder surfaceHolder, MySurfaceView surface) { mSurfaceHolder = surfaceHolder; mSurface = surface; } public void setRunning(boolean run) { mRunning = run; } @Override public void run() { Canvas c; while (mRunning) { c = null; try { c = mSurfaceHolder.lockCanvas(); if (c != null) { mSurface.doDraw(c); } } finally { // when exception is thrown above we may not leave the surface in an inconsistent state if (c != null) { try { mSurfaceHolder.unlockCanvasAndPost(c); } catch (Exception e) { } } } } } }
代码部分来自LunarLander
Android SDK中的示例,更具体地说LunarView.java
.
更新代码以匹配Android 6.0(API级别23)中的改进示例,产生以下结果:
import android.graphics.Canvas; import android.view.SurfaceHolder; public class MyThread extends Thread { /** Handle to the surface manager object that we interact with */ private final SurfaceHolder mSurfaceHolder; private final MySurfaceView mSurface; /** Used to signal the thread whether it should be running or not */ private boolean mRunning = false; /** Lock for `mRunning` member */ private final Object mRunningLock = new Object(); public MyThread(SurfaceHolder surfaceHolder, MySurfaceView surface) { mSurfaceHolder = surfaceHolder; mSurface = surface; } /** * Used to signal the thread whether it should be running or not * * @param running `true` to run or `false` to shut down */ public void setRunning(final boolean running) { // do not allow modification while any canvas operations are still going on (see `run()`) synchronized (mRunningLock) { mRunning = running; } } @Override public void run() { while (mRunning) { Canvas c = null; try { c = mSurfaceHolder.lockCanvas(null); synchronized (mSurfaceHolder) { // do not allow flag to be set to `false` until all canvas draw operations are complete synchronized (mRunningLock) { // stop canvas operations if flag has been set to `false` if (mRunning) { mSurface.doDraw(c); } } } } // if an exception is thrown during the above, don't leave the view in an inconsistent state finally { if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } } }
但是,这个类仍不适用于上述设备.我得到一个黑屏,应用程序停止响应.
唯一(我发现)解决问题的方法是添加System.out.println("123")
调用.并且在循环结束时添加一个短暂的睡眠时间,结果是提供相同的结果:
try { Thread.sleep(10); } catch (InterruptedException e) { }
但这些都不是真正的修复,是吗?这不奇怪吗?
(根据什么改变我对代码做,我也能看到一个异常错误日志.有许多开发商 用 了 同样的 问题,但遗憾的是没有确实提供了我的(设备特定的)情况下的解决方案.
你能帮我吗?
目前正在为我工作的是什么,虽然没有真正解决问题的原因但是表面上对抗症状:
Canvas
操作我的渲染线程调用子类doDraw(Canvas canvas)
上的自定义方法SurfaceView
.
在该方法中,如果我删除所有来电Canvas.drawBitmap(...)
,Canvas.drawRect(...)
对等操作Canvas
,应用程序不会再冻结.
单个调用Canvas.drawColor(int color)
可能会留在方法中.即使是昂贵的操作,比如BitmapFactory.decodeResource(Resources res, int id, Options opts)
读取/写入我的内部Bitmap
缓存也是如此.没有冻结.
显然,没有任何绘图,这SurfaceView
并不是真的有用.
我的渲染线程执行的方法:
@Override public void run() { Canvas c; while (mRunning) { c = null; try { c = mSurfaceHolder.lockCanvas(); if (c != null) { mSurface.doDraw(c); } } finally { if (c != null) { try { mSurfaceHolder.unlockCanvasAndPost(c); } catch (Exception e) { } } } } }
简单地在循环中添加一个短暂的睡眠时间(例如在结尾处)可以修复LG G4上的所有冻结:
while (mRunning) { ... try { Thread.sleep(10); } catch (Exception e) { } }
但是谁知道为什么这样可行,如果这确实解决了问题(在所有设备上).
System.out
与Thread.sleep(...)
上述相同的事情也可以使用System.out.println("123")
,奇怪的是.
这是我从以下内容开始渲染线程的方式SurfaceView
:
@Override public void surfaceCreated(SurfaceHolder surfaceHolder) { mRenderThread = new MyThread(getHolder(), this); mRenderThread.setRunning(true); mRenderThread.start(); }
在以下延迟执行中包含这三行时,应用程序不再冻结:
new Handler().postDelayed(new Runnable() { @Override public void run() { ... } }, 10);
这似乎是因为在开始时只有一个可能的死锁.如果清除(延迟执行),则没有其他死锁.该应用程序运行后就好了.
但是当离开时Activity
,应用程序再次冻结.
除了LG G4,索尼Xperia Z4,华为Ascend Mate 7,HTC M9(以及其他一些设备)外,该应用程序在数千台设备上运行良好.
这可能是特定于设备的故障吗?人们肯定会听说过这个......
所有这些"解决方案"都很苛刻.我希望有更好的解决方案 - 我打赌有!