【问题标题】:Unable to finish a paused activity until it regains focus在重新获得焦点之前无法完成暂停的活动
【发布时间】:2017-02-19 06:16:15
【问题描述】:

我想完成()一个在透明活动下的暂停活动。

我有一个活动,称为活动 A。活动 A 处于活动状态时会发生两件事;

  • 我们可以启动(透明的)Activity B

  • 我们可以接收异步回调来完成活动 A。

这两个动作发生得非常接近。代码是这样的

public class ActivityA extends Activity
{
    public class DataHandler implements ContentLoader.OnDataListener
    {
        @Override
        public void onData(Cursor data)
        {
            _binder.bind(data);
        }
    }

    //If this callback is executed while Activity A is paused, it will not go into onStop until it the activity above it is finished
    private class LoaderCallbacks extends ContentLoader.LoaderCallbacks
    {
        public LoaderCallbacks(ContentLoader loader)
        {
            super(loader);
        }

        @Override
        public void onLoadFinished(
                Loader<Cursor> loader,
                Cursor cursor)
        {
            if (cursor == null || cursor.getCount() <= 0)
            {
                Log.d("Eric", "* ON FINISH *");
                finish();
                finishagain();
                return;
            }

            super.onLoadFinished(loader, cursor);
        }
    }
}

在这个活动显示的列表片段内部有一个启动活动B的机制

public class FragmentA extends ListFragment
{
    //Some fragment functions here...

        @Override
    public void onListItemClick(
            ListView list,
            View view,
            int position,
            long id)
    {
            Intent intent = new Intent();
            intent.setAction(Intent.LAUNCH_ACTIVITY_B);
            getActivity().sendBroadcast(intent)
    }
}

我的问题是在活动 B 启动后调用完成活动 A 的回调时,活动 A 不会立即完成。它一直处于暂停状态,直到 Activity B 完成,然后两者都完成。这是一个竞争条件,我已经通过在暂停状态下使用简单的等待线程再次尝试完成来确认这一点。正如预期的那样,所有的完成调用都在主线程上执行。

private void finishagain()
{
    Handler handler = new Handler();
    int LOCK_HOME_DELAY = 5000;
    handler.postDelayed(new Runnable()
    {
        public void run()
        {
            if (notfinished){
                Log.d("Eric", "*************** FINISH AGAIN ****************");
                finish(); //Does nothing while the activity is paused
            }
            else{
                Log.d("Eric", "* Times up do nothing *");
            }

        }
    }, LOCK_HOME_DELAY);
}

这是我的日志(一些包名可能会被编辑)

    10-10 18:23:05.168 74-98/system_process I/ActivityManager: Displayed somepackage/com.eric.activity.A: +894ms
    10-10 18:23:07.135 74-98/system_process I/ActivityManager: Displayed somepackage/com.eric.activity.B: +343ms
    10-10 18:23:07.102 547-547/somepackage D/Eric: * Times up do nothign *
    10-10 18:23:07.231 547-547/somepackage D/Eric: * ON FINISH *
    10-10 18:23:08.220 547-547/com.eric.Status D/Eric: * Times up do nothign *
    10-10 18:23:08.305 547-547/com.eric.Status D/Eric: * Times up do nothign *
    10-10 18:23:12.305 547-547/com.eric.Status D/Eric: *************** FINISH AGAIN ****************
    10-10 18:23:12.305 74-668/system_process W/ActivityManager: Finishing task with all activities already finished
    10-10 18:23:12.305 74-668/system_process W/ActivityManager: Duplicate finish request for ActivityRecord{3627639c u0 somepackage/com.eric.activity.A t2292 f}

(注意时间戳 - 我在 :07 秒调用完成,但它没有完成。finishAgain() 在 :12 秒再次调用完成,它似乎在这里关闭,但我也看到它稍后完成。另外请注意“重复的完成请求” - 对我来说,完成看起来像是排队或其他东西)。

如何让 Activity A 在透明 Activity B 下方暂停时完成?

说实话,我很惊讶这是一个问题;我认为 backstack 上的活动应该很容易被杀死,但可能不是那些处于 onPause 状态的活动?我一直无法找到这方面的文档,也许有人知道相关的文档/代码?

编辑看我的回答

【问题讨论】:

  • 不确定你的整个项目是什么,但你能补充一些关于你为什么要这样做的见解吗?也许您可以在 ActivityB 中覆盖onBackPressed(),并且永远不允许应用返回到 Activity A,除非您强行杀死​​它?听起来很老套,只是一个想法。
  • @McAdam331 动机是简单的设计决策;只是想让用户看到 Activity A 下面的 Activity。具体来说,Activity A 是一个“警告消息”活动——当启动活动 B 时,警告将过期,用户应该看到它下面的活动。 Activity B 大部分是透明的并显示动作的状态(例如“加载模块 1 of 10”)
  • 您是否考虑过使用 DialogFragment 来获得这种体验?
  • DialogFragment 将是正常的方式,但此对话框是异步启动和控制的,并且是跨进程的。对话片段仍然与特定活动相关联 - 我们需要随时在任何内容上启动此对话。
  • 听起来不错。我真的不知道您在这里的活动问题,所以我只是想了解 UX 的目的。让我知道你发现了什么。 :)

标签: android multithreading


【解决方案1】:

编辑查看services/java/com/android/server/am/ActivityStack.java,找到返回“ActivityRecord 的重复完成请求...”日志的行(链接版本的第 3424 行)。它位于一个名为 finishActivityLocked(...) 的函数中,该函数检查 finished 标志。

然后我在同一函数的第 3505 行遇到了这个

else if (r.state != ActivityState.PAUSING) {
            // If the activity is PAUSING, we will complete the finish once
            // it is done pausing; else we can just directly finish it here.
            if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r);
            return finishCurrentActivityLocked(r, index,
                    FINISH_AFTER_PAUSE) == null;
        } 

我没有时间看太深,但我可能遇到了这个条件,或者类似的条件。我不确定什么构成 PAUSING 与 PAUSED (列出了两个状态)。也许这不是我陷入的确切逻辑,但显然活动堆栈的状态机非常细微,并且它们确实具有排队完成请求的机制。

因此,总而言之,我无法提供明确的行号,但我相当有信心

1) 活动状态机排队完成请求

2)具体条件在ActivityState.java的4k行某处。

原帖

我最终为这个问题写了一个解决方法。您还记得,问题是对在透明活动下暂停的活动的完成()调用被延迟。我的解决方法是在没有动画的情况下启动暂停的活动并立即终止它。它在性能方面相对不明显。

仍然不确定核心问题,而且我没有花太多时间深入研究 AOSP 源代码,因此我提供了悬赏,以提供有关完成如何处理暂停活动的任何明确解释。

private boolean notfinished;

/**
 * This function is used to finish an IR if it is stuck in a paused state.
 *
 * An activity will not finish() while paused underneath another activity. This function will
 * force a finish immediately by relaunching our activity and killing it immediately.
 *
 * This function assumes the launchmode is singletask or singleinstance, and does
 * not contain flags that clear or change stack (like FLAG_ACTIVITY_CLEAR_TOP)
 */
private void finishagain()
{
    Handler handler = new Handler();
    handler.postDelayed(new Runnable()
    {
        public void run()
        {
            if (notfinished)
            {
                Log.d(TAG, "ContentActivity couldn't finish immediately. " +
                        "This may happen if the Activity is paused when finish is called. Trying again");
                Intent i = getIntent();
                i.putExtra(MyConstants.FINISH_STATUS_FLAG, true);
                i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                startActivity(i) ;
            }
        }
    }, DELAY_CHECK_FINISHED);
}    

@Override
public void onStart()
{
    super.onStart();
    notfinished = true;
}

@Override
public void onResume()
{
    super.onResume();
    //There is a case where we need to take this activity out of paused state to kill it
    if (getIntent().getBooleanExtra(MyConstants.FINISH_STATUS_FLAG, false))
    {
        Log.d(TAG, "Finishing ContentActivity");
        finish();
        overridePendingTransition(0, 0); //Blocks the default finishing transition animation
    }
}

@Override
public void onStop()
{
    super.onStop();
    notfinished = false;
}

//sample useage
  private class LoaderCallbacks extends ContentLoader.LoaderCallbacks
  {
    public LoaderCallbacks(ContentLoader loader)
    {
        super(loader);
    }

    @Override
    public void onLoadFinished(
            Loader<Cursor> loader,
            Cursor cursor)
    {
        if (cursor == null || cursor.getCount() <= 0)
        {
            finish();
            finishagain();
            return;
        }

        super.onLoadFinished(loader, cursor);
        setContentDescription();
    }
}

【讨论】:

    【解决方案2】:

    您可以通过调用其 finish() 方法来关闭一个活动。您还可以通过调用 finishActivity() 来关闭您之前启动的单独活动。

    您可以访问开发者页面here

    所以现在,如果你习惯了很多活动,那么完成的方法是..

    1. 启动其他活动,只需要类、意图和请求代码。

      public void startAcc(View v){
      Intent i = new Intent(this, SecondActivity.class);
      startActivityFromChild(this,i,0);
      }
      
    2. 暂停完成其他人的活动。只需要子类和你应该用来初始化的请求代码。

      finishActivityFromChild(MainActivity.this,0);
      

    在您的情况下,当 loadercallback 为 onLoaderFinished 时,应使用 startActivityFromChild 来启动其他活动,并在调用 finish() 之前使用 finishActivityFromChild

    希望对你有帮助。

    【讨论】:

      【解决方案3】:

      我不知道你为什么要把这么简单的事情给自己弄得如此困难和复杂。在活动 B 完成加载后完成活动 A。

      我不明白ContentLoaderLoaderCallbacksFragmentListFragment 等的用例。 我什至想知道为什么 Fragments 仍然没有被弃用?!!!一个沉重的 2209 行类,只是为了在另一个视图中显示一个 ViewGroup?!好吧,我可以自己膨胀并将该 ViewGroup 添加到视图中,为什么我必须使用片段?!无论如何。

      他们创建 Application 类是有原因的。你为什么不用那个?!

      我只是假设您的问题是:如何在 Activity B 完成加载后完成 Activity A:

      "我们可以启动(一个透明的)Activity B 我们可以接收到一个异步回调来完成活动 A。”

      如果您想通过事件获得通知,请为此创建一个事件侦听器,而不是使用那些复杂的类和方法。

      方法如下:

      你有一个这样的应用程序类:

      public class App extends Application{
      
          private static ArrayList<ActivityLoadingFinishedListener> activityListeners;
      
      public static interface ActivityLoadingFinishedListener{
          void OnActivityFinishedLoading();
      }
      
      public static void addActivityListener(ActivityLoadingFinishedListener listener){
          if(activityListeners == null){
              activityListeners = new ArrayList<ActivityLoadingFinishedListener>();
          }
          activityListeners.add(listener);
      }
      
      public static void removeAllActivityListeners(){
          if(activityListeners != null){
              activityListeners.clear();
          }
      }
      
      public static void notifyAllActivityListeners(){
          if(activityListeners != null){
              for(ActivityLoadingFinishedListener alfl:activityListeners){
                  alfl.OnActivityFinishedLoading();
              }
          }
      }
      }
      

      然后在活动 A 中:

              Intent intent = new Intent(A.this, B.class);
              App.addActivityListener(new ActivityLoadingFinishedListener() {
      
                  @Override
                  public void OnActivityFinishedLoading() {
                      finish();
                  }
              });
              startActivity(intent);
      

      然后在活动 B 中:

      @Override
      protected void onResume() {
          super.onResume();
          App.notifyAllActivityListeners();
      }
      

      这只是向您展示可以轻松创建自己的事件的示例。它们非常有用、简单、快速和可靠。

      您可以更改我的示例以满足您的需求。

      对于您的问题:“当 Activity A 在透明的 Activity B 下方暂停时,我怎样才能让它完成?”

      您可以使用相同的方法。当Activity B恢复时,它应该通知所有的监听器(Activity A是其中之一)。

      【讨论】:

      • 感谢您的回复。我的问题不是如何通知听众,而是为什么我的完成活动的通知没有立即发生。
      【解决方案4】:

      嗯,这个问题比看起来更棘手,透明的 Activity 会导致问题。你解释得很好,但是人们通常不会回答这个问题,他们会泄露一些他们熟悉的东西。

      "如果一个活动失去焦点但仍然可见(即一个新的 非全尺寸或透明活动的重点是您的 活动),它被暂停。一个暂停的活动是完全活跃的(它 维护所有状态和成员信息,并保持连接到 窗口管理器),但可以在内存极低的情况下被系统杀死 情况。”see android doc.

      你说:

      //如果在Activity A暂停的时候执行这个回调,它会 在上面的活动完成之前不要进入 onStop

      当活动 A 可见时,您不能进入停止状态。Android destroying activities, killing processes
      这可能会有所帮助:

      View topLevelLayout = findViewById(R.id.top_layout);
      topLevelLayout.setVisibility(View.INVISIBLE);
      

      在我的回答中,默认情况是异步的(它是广播)。 在创建时为 本地广播 注册每个 Activity(您想稍后杀死,ActivityA)。 当它进入后台时(暂停状态), (即,一个新的活动到达堆栈的顶部), 它的onPause() {onStop()} 将被调用,但它仍然可以接收广播。 您只需要确保在 onDestroy() 而不是在 onPause() {onStop()} 中调用 unregisterReceiver()。

      这里有一些示例代码可以帮助您了解这个想法:

          /** your "kill" method (ActivityA) **/
          Intent broadcastIntent = new Intent();
          broadcastIntent.setAction("com.package.ACTION_KILL");//some string
          //  sendBroadcast(broadcastIntent);//global broadcast
          LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
      
           /**    The receiver (ActivityA) **/
              BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver(); 
              protected void onCreate(Bundle savedInstanceState) {
      
              super.onCreate(savedInstanceState);
      
          IntentFilter intentFilter = new IntentFilter();
          intentFilter.addAction("com.package.ACTION_KILL");//some string
          LocalBroadcastManager.getInstance(this).registerReceiver(myBroadcastReceiver     {
           @Override
           public void onReceive(Context context, Intent intent) {
                Log.d("onReceive","KILL in progress");
                //At this point you should do your stuff ;O)
                finish();//kill it
               //System.exit(0)// to clear static variables
               //android.os.Process.killProcess(android.os.Process.myPid());//"cave man" kill                          }
            }, intentFilter);
         }
      
          protected void onDestroy(
          {
            LocalBroadcastManager.getInstance(this).unregisterReceiver(myBroadcastReceiver );
             super.onDestroy();
          }
      

      https://developer.android.com/training/basics/activity-lifecycle/index.html https://developer.android.com/reference/android/app/Activity.html https://developer.android.com/reference/android/content/BroadcastReceiver.html

      【讨论】:

        猜你喜欢
        • 2018-12-30
        • 1970-01-01
        • 1970-01-01
        • 2011-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-23
        • 1970-01-01
        相关资源
        最近更新 更多