Android - 在活动之间共享表面视图或阻止绘制任务以在切换活动时锁定主线程



我正在开发一个应用程序,该应用程序在其所有活动中都使用公共标头。标头包含一种指示任务完成的自定义进度条。"进度条"是通过对 SurfaceView 进行子类化来实现的,绘图操作由内部ExecutorService管理。

告诉"进度栏"运行某个动画的任务由单例自定义 AsyncTaskManager 发出,该管理器包含对自定义SurfaceView和当前活动的引用。

单例管理器控件的某些AsyncTask是在自定义活动onCreate方法上执行的,因此有时 AsyncTaskManager 会在实际显示活动之前通知进度栏进行动画处理。用户也可能选择在进度栏的绘图Runnable任务完成之前切换活动。为了更好地解释,当我切换到某些活动时,就会发生这种情况:

  1. oldActivity 告诉ExecutorService取消它在 SurfaceView 画布上绘制Future任务。

  2. newActivity的onCreate被触发并发出AsyncTaskManager单例以启动新的异步任务。

  3. AsyncTask 在其 onPreExecute 中告诉进度条开始在其画布上绘制。

  4. ExecutorService管理绘图Runnable,而绘图又锁定SurfaceHolder

  5. 当 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
                            }
                        }
                    }

无论如何,谢谢!

最新更新