我正在开发一个应用程序,该应用程序在其所有活动中都使用公共标头。标头包含一种指示任务完成的自定义进度条。"进度条"是通过对 SurfaceView 进行子类化来实现的,绘图操作由内部ExecutorService
管理。
告诉"进度栏"运行某个动画的任务由单例自定义 AsyncTaskManager 发出,该管理器包含对自定义SurfaceView
和当前活动的引用。
单例管理器控件的某些AsyncTask
是在自定义活动onCreate
方法上执行的,因此有时 AsyncTaskManager 会在实际显示活动之前通知进度栏进行动画处理。用户也可能选择在进度栏的绘图Runnable
任务完成之前切换活动。为了更好地解释,当我切换到某些活动时,就会发生这种情况:
-
oldActivity 告诉
ExecutorService
取消它在 SurfaceView 画布上绘制Future
任务。 -
newActivity的onCreate被触发并发出AsyncTaskManager单例以启动新的异步任务。
-
AsyncTask 在其 onPreExecute 中告诉进度条开始在其画布上绘制。
-
ExecutorService
管理绘图Runnable
,而绘图又锁定SurfaceHolder
-
当 AsyncTask 完成时,在其 onPostExecute 方法中,告诉 Surfaceview 绘图
Runnable
绘制不同的东西根据结果。
我遇到的问题是有时(并非总是 - 似乎是随机的,但也许它与任务线程池有关(,在启动新活动时,应用程序会跳过第 xx 帧,其中 xx 显然是随机的(有时它会跳过 ~30 帧,有时跳过 ~ 300,其他时候应用程序会获得 ANR(。
几天来我一直在尝试解决这个问题,但无济于事。
我认为问题可能是以下之一或两者的组合:
-
绘图线程不会及时取消/结束,从而导致 SurfaceHolder 保持锁定状态,从而阻止活动在继续暂停/恢复时控制视图,从而导致主线程跳过帧。动画在计算方面绝不是繁重的(几个点移动(,但它需要持续至少 300 毫秒才能正确通知用户。
-
单例 AsyncTaskManager 保存对"离开活动"的 SurfaceView 的引用,防止前者被销毁,直到释放 surfaceholder 并导致跳帧。
我更倾向于相信第二个问题是什么让Coreographer生气,所以这导致了以下问题:
如何在所有活动之间共享相同的(与同一实例中一样(surfaceView(或任何视图,实际上是(,或者允许销毁和重新创建SurfaceView
的当前实例,而无需等待线程加入/中断?
就像现在一样,在活动之间切换时SurfaceView
被销毁/重新创建,如果它的绘制线程在新活动开始其生命周期时停止,我不会反对它。
这是自定义的AsyncTaskManager,它包含对SurfaceView的引用。
public class AsyncTaskManager {
private RefreshLoaderView mLoader;
//reference to the customactivity holding the surfaceview
private CustomBaseActivity mActivity;
private final ConcurrentSkipListSet<RequestedTask> mRequestedTasks;
private volatile static AsyncTaskManager instance;
private AsyncTaskManager() {
mRequestedTasks = new ConcurrentSkipListSet<RequestedTask>(new RequestedTaskComparator());
}
public void setCurrentActivity(CustomBaseActivity activity) {
mActivity = activity;
if (mLoader != null) {
mLoader.onDestroy();
}
mLoader = (RefreshLoaderView) mActivity.getViewById(R.id.mainLoader);
}
这是当AsyncTask(上述代码片段中的RequestTask(发生的情况已执行
@Override
protected void onPreExecute() {
if (mLoader != null) {
mLoader.notifyTaskStarted();
}
}
@Override
protected Integer doInBackground(Void... params) {
//do the heavy lifting here...
}
@Override
protected void onPostExecute(Integer result) {
switch (result) {
case RESULT_SUCCESS:
if (mLoader != null) {
mLoader.notifyTaskSuccess();
}
break;
//TELLS THE SURFACE VIEW TO PLAY DIFFERENT ANIMATIONS ACCORDING TO RESULT ...
这是 CustomBaseActivity,它保存所有其他活动继承的 SurfaceView。
public abstract class CustomBaseActivity extends FragmentActivity {
private volatile RefreshLoaderView mLoader;
//...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_base);
mLoaderContainer = (FrameLayout) findViewById(R.id.mainLoaderContainer);
mLoader = (RefreshLoaderView) findViewById(R.id.mainLoader);
//other uninteresting stuff goin on ...
还有SurfaceView的代码:
public class RefreshLoaderView extends SurfaceView implements SurfaceHolder.Callback {
private LoaderThread mLoaderThread;
private volatile SurfaceHolder mHolder;
private static final int ANIMATION_TIME = 600;
private final ExecutorService mExecutor;
private Future mExecutingTask;
public RefreshLoaderView(Context context) {
super(context);
...
init();
}
private void init() {
mLoaderThread = new LoaderThread();
...
}
@Override
public void surfaceChanged(SurfaceHolder holder, int arg1, int arg2, int arg3) {
...
mHolder = this.getHolder();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//uninteresting stuff here
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopThread();
}
private void stopThread() {
mLoaderThread.setRunning(false);
if (mExecutingTask != null) {
mExecutingTask.cancel(true);
}
}
private void startThread() {
if (mLoaderThread == null) {
mLoaderThread = new LoaderThread();
}
mLoaderThread.setRunning(true);
mExecutingTask = mExecutor.submit(mLoaderThread);
}
public void notifyTaskStarted() {
stopThread();
startThread();
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_STARTED);
}
public void notifyTaskFailed() {
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_FAILED);
}
public void notifyTaskSuccess() {
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_SUCCESS);
}
private class LoaderThread implements Runnable {
private volatile boolean mRunning = false;
private int mAction;
private long mStartTime;
private int mMode;
public final static int ANIMATION_TASK_STARTED = 0;
public final static int ANIMATION_TASK_FAILED = 1;
public final static int ANIMATION_TASK_SUCCESS = 2;
private final static int MODE_COMPLETING = 0;
private final static int MODE_ENDING = 1;
public LoaderThread() {
mMode = 0;
}
public synchronized boolean isRunning() {
return mRunning;
}
public synchronized void setRunning(boolean running) {
mRunning = running;
if (running) {
mStartTime = System.currentTimeMillis();
}
}
public void setAction(int action) {
mAction = action;
}
@Override
public void run() {
if (!mRunning) {
return;
}
while (mRunning) {
Canvas c = null;
try {
c = mHolder.lockCanvas();
synchronized (mHolder) {
//switcho quello che devo animare
if (c != null) {
switch (mAction) {
case ANIMATION_TASK_STARTED:
animationTaskStarted(c);
break;
case ANIMATION_TASK_FAILED:
animationTaskFailed(c, mMode);
break;
case ANIMATION_TASK_SUCCESS:
animationTaskSuccess(c, mMode);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
mHolder.unlockCanvasAndPost(c);
}
}
}
}
private void animationTaskStarted(Canvas canvas) {
//do an animation here
}
private void animationCloseLoaderCycle(Canvas canvas) {
//do stuff here ...
} else {
mStartTime = System.currentTimeMillis();
mMode = MODE_ENDING;
}
}
private void queryThreadClose() {
mProgress = 0;
mMode = MODE_COMPLETING;
mRunning = false;
}
private void animationTaskFailed(Canvas canvas, int mode) {
switch (mode) {
case MODE_COMPLETING:
animationCloseLoaderCycle(canvas);
break;
case MODE_ENDING:
if (System.currentTimeMillis() - mStartTime < ANIMATION_TIME) {
//notify user task is failed
} else {
queryThreadClose();
}
break;
}
}
private void animationTaskSuccess(Canvas canvas, int mode) {
switch (mode) {
case MODE_COMPLETING:
animationCloseLoaderCycle(canvas);
break;
case MODE_ENDING:
if (System.currentTimeMillis() - mStartTime < ANIMATION_TIME) {
//notify user task is failed
} else {
queryThreadClose();
}
break;
}
}
}
public void onPause() {
stopThread();
}
public void onStop() {
stopThread();
}
public void onDestroy() {
stopThread();
}
}
当 Coreographer 警告我我跳过帧时使用 DDMS 表明通常有大约 30 个线程(守护程序和普通线程(正在运行,其中异步任务、主线程和绘图任务正在等待某些东西。(另外,我如何检查他们在等什么?
提前感谢您的帮助。
编辑:根据DDMS线程视图,这些是挂起时的主线程调用:
at hava.lang.Object.wait(Native Method)
at java.lang.Thread.parkFor(Thread.java:1205)
at sun.misc.Unsafe.park(Unsafe.java:325)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:157)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:813)
...
我最终解决了这个问题。同步块中存在错误:
while (mRunning) {
Canvas c = null;
try {
//mistake was here
c = mHolder.lockCanvas();
synchronized (mHolder) {
if (c != null) {
//do stuff
}
}
}
我在同步块之外获取画布,因此在需要销毁/重新创建活动时导致死锁。
在同步块内移动c = mHolder.lockCanvas();
解决了这个问题。
最后工作代码如下:
synchronized (mHolder) {
c = mHolder.lockCanvas();
if (c != null) {
switch (mAction) {
//do stuff
}
}
}
无论如何,谢谢!