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

时间:2014-03-22 14:59:30

标签: java android concurrency surfaceview

我正在开发一个在其所有活动中使用通用标头的应用程序。 标题包含一种表示任务完成的自定义进度条。 "进度条"通过继承SurfaceView实现,绘图操作由内部ExecutorService管理。

告诉"进度条的任务"运行某个动画由Singleton自定义AsyncTaskManager发出,它包含对自定义SurfaceView和当前活动的引用。

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

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

  2. 触发newActivity的onCreate并发出AsyncTaskManager 单身人士开始新的AsyncTask。

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

  4. ExecutorService依次管理图纸Runnable 锁定SurfaceHolder

  5. 当AsyncTask完成时,在其onPostExecute方法中, 告诉surfaceview drawing Runnable画一个不同的东西 根据结果​​。

  6. 我遇到的问题是SOMETIMES(并非总是 - 似乎是随机的,但可能与任务线程池有关),在启动新活动时,应用程序跳过帧xx,其中xx显然是随机的(有时它跳过~30帧,其他时间约为300,其他时间应用获得ANR)。

    我一直试图解决这个问题已有几天了,但无济于事。

    我认为问题可能是以下之一,也可能是两者的结合:

    • 绘图线程不会及时取消/结束,从而导致SurfaceHolder保持锁定状态,从而阻止Activity在视图中执行onPause / onResume时控制View,从而导致主线程跳过帧。动画在计算方面并不重要(几个点移动)但是它需要持续至少300ms以正确通知用户。

    • 单例AsyncTaskManager保存对"离开活动"的SurfaceView的引用,防止前者被销毁,直到释放出曲面所有者并导致跳帧。

    我更倾向于认为第二个问题是让Coreographer感到愤怒,因此导致了以下问题:

    如何在所有活动之间共享相同的SAME(在同一实例中)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(上面的代码片段中的RequestedTask)时会发生这种情况 执行

        @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 ...
    

    这是包含所有其他活动继承的SurfaceView的CustomBaseActivity。

        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个线程(守护进程和正常)运行,其中asynctask,主线程和绘图任务正在等待某事。 (另外,我如何检查他们在等什么?)

    提前感谢您的帮助。

    编辑:这些是挂起时的主要线程调用,根据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)
    ...
    

1 个答案:

答案 0 :(得分:1)

我最终解决了这个问题。同步块中有一个错误:

while (mRunning) {
                    Canvas c = null;
                    try {
          //mistake was here
                        c = mHolder.lockCanvas();
                        synchronized (mHolder) {

                            if (c != null) {
                             //do stuff
                            }
                        }
                    }

我在同步块之外得到了画布,因此当需要销毁/重新创建活动时会导致死锁。

在synchronized块中移动c = mHolder.lockCanvas();解决了这个问题。

最后工作代码如下:

synchronized (mHolder) {
                        c = mHolder.lockCanvas();
                        if (c != null) {
                            switch (mAction) {
                                //do stuff
                            }
                        }
                    }

非常感谢!