直接调用onResume()的替代方法

10

我正在重写我的Android应用程序,以消除对onResume()的直接调用。

我的应用程序目前大部分工作都在onResume()内完成,然后将显示内容发布,这就是onResume()的结束。

@Override
public void onResume() {
    super.onResume();
    // get current date and time,
    //  and determine if daylight savings time is in effect.
    //...600 lines of code
    // output both the chart and report
    //
    image.setImageBitmap(heightChart);
    report.setImageBitmap(reportBitmap);
}

下一步是收集用户输入,这告诉我用户希望报告做出哪些更改(可能是新位置、新日期或新显示样式等)。具体操作如下:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    final int STATION_REQUEST = 1;
    int test = 1;
    switch (item.getItemId()) {
        case R.id.refresh: {
            userDateSet = false;
            onResume();
            return true;
        } // come to the present.

        //...200 lines of code
        default:
            return super.onOptionsItemSelected(item);
    }
}

正如这个示例所展示的,确定新用户命令后通过调用onResume()来重新生成输出。这是不好的实践,我已经知道了!!然而目前为止它表现良好,我真的不明白它的问题所在。

我考虑的解决方案是将这600行代码聚合到单独的程序中,并在onResume()onOptionsItemSelected()的多个点中调用该程序。

@Override
public void onResume() {
    super.onResume();
    myOnResumeCode();
}

onOptionsItemSelected() 内部执行以下操作

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    final int STATION_REQUEST = 1;
    int test = 1;
    switch (item.getItemId()) {
        case R.id.refresh: {
            userDateSet = false;
            myOnResumeCode();
            return true;
        } // come to the present.

    ... // Other statements
}

这种方法可行吗?如果不行,除了“重新编写整个代码”之外,是否有其他建议可以帮助我解决问题。我已经广泛地搜索了干净的解决方案,但却没有找到一个我能理解的。谢谢。


我正在发布悬赏,因为我想要一个非常清晰的陈述,关于我的提议解决方案是否可接受,如果是,为什么可以,那太好了;如果不行,为什么不行以及我该如何修复它。 - user1644002
7个回答

4

我不理解它有什么问题。

onResume() 方法本身是无害的。但是调用其超级方法 super.onResume(); 将让系统认为这是另一个恢复事件的发生。这将导致不必要的资源使用,用于刷新视图和类似的内部工作。因此,在任何情况下都必须避免显式调用生命周期回调方法。

这种方法可接受吗?

代码行数多少并不是判断是否可接受的标准。这是你需要问自己的问题。如果你认为整个代码需要在该事件中执行,那么就应该这样做。否则,你可以节省一些资源。

如果你正在做这样的事情:

public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.mnuEnableSomething:
            {
                refreshTheWholeUi();
                return true;
            }
        case R.id.mnuClearList:
            {
                refreshTheWholeUi();
                return true;
            }
    }
}

public void onResume() {
    super.onResume();
    refreshTheWholeUi();
}

那么将其更改为这样将是值得的。
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.mnuEnableSomething:
            {
                enableIt();
                return true;
            }
        case R.id.mnuClearList:
            {
                justClearTheList();
                return true;
            }
    }
}

public void onResume() {
    super.onResume();
    refreshTheWholeUi();
}

现在,进入主题

在您的回复之后,我仔细查看了您的问题,并发现了这个问题。

我的计划是将那600行代码移动到单独的类文件中。这将使它们远离受损,同时我可以在活动源文件中处理命令解码器

不完全正确,但已经非常接近了。忘记所有诸如活动生命周期、方法、类等复杂性,只关注计算机程序执行的基本层次。

程序总是逐行执行的。无论您如何排列代码,都没有任何区别。将程序适当地组织成方法、类等是为了方便程序员。对于系统来说,它总是一系列行。因此,在执行重任务时,用户界面可能变得无响应,因为它必须等待轮到它执行。

那么如何并行工作呢?

多线程……!

它并不像听起来那么复杂。

您需要定位使用资源更多的代码的最关键部分,并将其移动到另一个线程中。

我在这里说明了如何进行多线程操作。

public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.mnuProceessImageAction:
            {
                //Let user know that a background operation is running
                //with a progressbar or something
                processImage(mImage);
                return true;
            }
    }
}

private void processImage(Object image) {
    new Thread(new Runnable(){
        public void run() {
        //Doing all the heavy duty here.
        //.............................
        //Now you have the result. Use it to update the UI.
        //(UI can be updated only from the UI thread)
        runOnUiThread(new Runnable(){
                public void run() {
                    updateTheUiWithTheImage(proccessedImage);
                }
        });
        }
    }).start();

}

private void updateTheUiWithTheImage(Object image) {
    try {
        //Hide progressbar if you have one
        //Now it wont make the UI to struggle to use it.
    } catch(NullPointerException e) {
        e.printStackTrace;
    }
}

这是最基本的形式。当然还有其他选择(例如AsyncTask)。您可以在网上轻松找到更多相关信息(尝试搜索“Android中的多线程”)。如有疑问,请随时提出。


“但是调用它的超级方法super.onResume()会让系统认为这是恢复事件的另一个发生。”不,不会。一旦系统调用了onResume(),它就“知道”客户端(在本例中为Activity)已被通知有关生命周期事件的信息。它不关心客户端进一步如何处理其方法。 - Onik
那么为什么您认为,“在不调用super.onResume()的情况下使用myOnResumeCode()的意图避免了不必要的方法调用,并且是更优化的解决方案。”呢? - Bertram Gilfoyle
这是我看待这个问题的关键所在。在构建我的解决方案时,我可以轻松避免超级调用。没有人说不调用super是危险的。更仔细地选择哪些命令需要伴随完全重新计算的建议很有吸引力。我需要进行实验以查看它是否适用于我的情况。在我的Note 8上,完全刷新需要约25毫秒的执行时间。在省电模式下,大约需要50毫秒。 - user1644002
我仔细查看了这个问题。我已经更新了答案。我相信它会对你有所帮助。 - Bertram Gilfoyle
我需要做两件事情,将主要计算的复杂性物理上移出主源文件,并清理直接onResume调用的问题。这个应用程序中最持久的工作在于命令/响应部分,而不是核心计算。这两个都在同一个源文件中,这非常危险,容易出现编辑错误。现在这两件事情都完成了,它运行良好。这本质上是一个运行并完成的应用程序(每日潮汐预测)。它大约需要1/8秒的数据准备、数值计算和图形输出。 - user1644002
1
我会尝试您提出的多线程方法,将我的繁重任务移至后台。谢谢,Ahamad。 - user1644002

3

考虑每次调用 super.onResume() 时执行的 ActivityonResume() 源代码:

protected void onResume() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
    getApplication().dispatchActivityResumed(this);
    mActivityTransitionState.onResume();
    mCalled = true;
}

在这段代码中,mActivityTransitionState.onResume() 调用 resetViews() 并对窗口中的视图进行进一步操作。虽然您已经发现它可以工作,但所有方法调用都会浪费 CPU 时间并且实际上是冗余的,因此第一种方法效率低下。

另一方面,使用 myOnResumeCode() 而不调用 super.onResume() 可以避免不必要的方法调用,是更优化的解决方案。

此外,600 行代码数量相当大。如果这些行在主线程上运行,它会冻结 UI,使应用程序看起来反应不够灵敏。最好在后台线程上进行计算,并在主线程上发布视图更改。

我没有听到不调用我的代码中的 super 的恐惧。

这似乎是对 Activity 生命周期方法用途的误解。这些方法是系统用来通知监听器有关事件发生的回调。当系统调用 onResume() 时,您会收到这样的通知。如果删除 super.onResume(),您将收到异常,这在链接的源代码中明确说明,并且是系统在调用其 onResume() 时唯一要求的。它适用于所有其他生命周期方法 - 通知 OS。系统不关心 Activity 是否再次“手动”调用它们。当 Activity 在前台时,您可以随意调用 onResume(),浪费 CPU 时间并使应用程序对用户看起来不够灵敏。

同样,重写的 onResume() 是一个回调(“监听器”),不能影响系统行为,与 finish() 方法相反,后者影响系统行为,表示:“嘿,系统,我完成了,想被你杀死”。这些类型的方法可以视为对系统的请求。

更新

他们甚至告诉您在何处放置应用程序代码吗?

您可以自由地将代码放置在任何您想要的位置。系统只是通过生命周期方法的调用通知您,例如,您的内容对用户可见或隐藏。因此,根据生命周期事件将代码放在合理的“位置”是一个问题。

这是否表示可以直接调用 onResume()?有明确反对这种做法的强烈声明。

这是毫无意义的,但正如你所见,它能起作用。"周五不吃肉",但谁说不能呢? :)

Onik,非常感谢您的评论。不幸的是,我的所有解释都掩盖了问题:如果直接调用onResume()确实存在问题,那么我是否可以通过将其分成两部分来解决该问题,即先调用super,然后调用我的例程。当我的命令解码器完成时,调用我的例程来更新显示。这样做是否解决了强烈声明的不要自己调用onResume()的问题?谢谢,我会阅读您建议的内容,此时我只是想澄清这个问题。顺便说一句,延迟是无法察觉的。 - user1644002
如果我深入了我的onResume代码,只做最后的99%(即super调用之后的部分),这样安全吗?发生这种情况的时间是当用户通过触摸或通过onOptionsItemSelected滑动给我一个命令时。 - user1644002
Onik,我在讨论“myOnResume()”时提到了不调用super。除了删除super调用之外,myOnResume与onResume完全相同。在myOnResume中工作的代码,包括最终的位图输出,在没有super调用的情况下也可以正常工作。 - user1644002
我记得在编写这个应用程序时曾经为生命周期的解释而苦恼。它们甚至告诉你在哪里放置应用程序代码吗? - user1644002
关于这个声明:系统不在意Activity是否“手动”再次调用它们。这是否意味着可以直接调用onResume()?但是,对此有明确的禁止规定,就像“周五不可食肉”一样强烈。 - user1644002
1
在这种情况下,按照建议的方式将onResume分成两部分非常容易,而且不需要通过诉讼来解决问题。建议的安排已经编码并且运行良好。 - user1644002

3
尽我所知,它的功能良好,但我实在不理解其中的问题。
您认为在手动调用onResume()的情况下调用super.onResume()是适当的,这是不安全的假设。
这种方法是可接受的吗?
这绝对是一种改进,值得采用。
600行代码是一个非常长的方法。如果您在代码审查中遇到此类问题,可能会被要求重构代码以提高可维护性。此外,根据您在这600行代码中所做的操作,将该逻辑移至后台线程可能更好。

我的计划是将这600行代码移动到一个单独的类文件中。这样可以在我处理活动源文件中的命令解码器时避免对它们造成损坏。我希望Java不会让这太痛苦。我非常希望活动源文件有多个编译单元,但我找不到方法实现。 - user1644002
2
在现代Android应用程序开发中,活动本身并没有太多的功能。您需要覆盖一些生命周期方法和其他必须存在于活动中的东西,并将几乎所有其他的事情委托给其他组件(片段、控制器/展示器等)。 - CommonsWare
这个简历需要在我的Note 8上大约25毫秒。我可以将循环计算隐藏在单独的类文件中,以达到清理代码的目的。有一个用于潮汐计算,一个用于日出时间,一个用于月亮时间和相位。这样更容易控制。但我没有听到需要进行重大改写的声音。我没有听到不调用代码中的super的恐惧。我可能不会得到对我的原始问题的明确答案。但如果它能工作,那就是好的。我只是不喜欢不得不实验来找出它应该如何工作。 - user1644002
@user1644002: "在我的Note 8上,这个onResume()方法大约需要25毫秒" -- 这将导致1-2帧的丢失(每帧16毫秒,而且在你的UI中除了这个onResume()调用之外还有其他开销)。你应该考虑将其移到后台线程中。"我没有听到在我的代码中不调用super的担忧" -- 在你的代码中,无论是原始代码还是修改后的代码,你都调用了super.onResume() - CommonsWare
这是一个运行一次就完成的应用程序。除非用户想要更改,否则它只会保持原样并显示结果。(这是每日潮汐预报)在我的修改后的代码中,我正在调用myOnResumeCode();而不是onResume();对于任何混淆,非常抱歉,谢谢。 - user1644002

1

根据我所了解的情况,它能够良好地工作,但是我真的不理解其中的问题。

我认为@CommonsWare和其他人已经指出了一个问题,即在用户交互时更新UI元素并调用onResume函数时可能会遇到问题。

这种方法可行吗?如果不行,除了“重写整个东西”之外,任何建议都对我很有帮助。

600行代码并不总是可维护的。您可以考虑将它们分成几个函数。然而,从我的角度来看,从单一位置同时完成所有事情仍然是一项困难的工作。

我强烈建议您在这种情况下使用ViewModel。实现和管理将变得更加简单。我附上了开发者文档中的示例实现。

public class UserActivity extends Activity {

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.user_activity_layout);
         final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
         viewModel.userLiveData.observer(this, new Observer() {
            @Override
             public void onChanged(@Nullable User data) {
                 // update ui.
             }
         });
         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                  viewModel.doAction();
             }
         });
     }
 }

ViewModel会像这样。

 public class UserModel extends ViewModel {
     public final LiveData<User> userLiveData = new LiveData<>();

     public UserModel() {
         // trigger user load.
     }

     void doAction() {
         // depending on the action, do necessary business logic calls and update the
         // userLiveData.
     }
 }

当您在ViewModel中操作一些数据时,由于我们实现了回调函数onChange,UI将得到更新。

实现回调函数的想法可以通过多种方式完成(例如定义接口然后覆盖函数)。如果能够正确地实现它们,代码将变得更加清晰。

从ViewModel文档中得知...

ViewModel还可以用作Activity的不同Fragment之间的通信层。每个Fragment都可以使用相同的键通过它们的Activity获取ViewModel。这样可以以解耦合的方式在片段之间进行通信,使它们永远不需要直接与其他片段交互。

public class MyFragment extends Fragment {
     public void onStart() {
         UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
     }
}

现在我认为,你的问题变得简单多了。你可以考虑将UI元素分解成几个Fragment,并通过不同的生命周期元素在那里处理更新。
希望这有所帮助!

Reaz,我的应用程序分为许多类和活动,总共约有10到12个。现在,在onResume期间通过调用单独的例程来完成输出的创建,因此我不需要进行超级调用。系统通过调用onResume告诉我何时可以运行。我的应用程序通过调用onOptionsItemSelected操作告诉我何时可以重新运行相同的代码。感谢您的建议,我正在努力有效地使用它,谢谢。 - user1644002

1

这是我的最终编码,感谢Ahamad的出色指导。 我现在不再调用onResume()。在各个地方,我调用myOnResume()。 part1()代码是所有扩展计算的部分。part2()代码是所有输出操作的部分。

@Override
    public void onResume()
    {   super.onResume();//
        myOnResume();
    }// end: onResume.

    void myOnResume()
    {   new Thread(new Runnable()
        {   public void run()                   //run #1
            {   part1();                        // do all the calculations in background.
                runOnUiThread(new Runnable()
                {   public void run()           //run #2
                    {   part2();                // do the display in the UI thread.
                    }                           //end of run#2
                });                             //end of runOnUiThread
            }                                   //end of run#1
        }).start();
    }                                           //end: myOnResume()

0

在开发者中,调用生命周期方法来执行某些任务是非常普遍的习惯,因为这样做很方便

但实际代码必须被适当地模块化。

既然您正在重写代码,我建议您迁移到MVVM或MVP架构,因为使用这些架构可以更好地管理您提到的情况。

无论您是否使用架构,都应该根据目的拆分代码。

例如,onResume()表示Activity/Fragment恢复时要执行的操作。对于onCreate()onDestroy()等也是如此。

一般来说,
1. 我们在onCreate中初始化不变的变量,并在onDestroy中释放它们。
2. 我们在onResume()中重新从后端获取数据或刷新UI。
3. 我们在onPause()中暂停任何正在进行的进程,如媒体播放等。
等等

根据您提供的代码示例,您提到了大约600行代码,我认为它们并没有执行相同的任务。

因此,您必须根据任务拆分代码。

private void refreshDayLightTime(){
    //refresh your time
}

private void performRefreshTasks(){
   //series of codes that are to be executed one after the other
   //ideally this method should call methods which perform specific tasks.
}

private void updateLaunchViews(Bitmap heightChart, Bitmap reportBitmap){
   image.setImageBitmap(heightChart);
   report.setImageBitmap(reportBitmap);
}

使用这种方法可以使您的代码更加清晰。主要是不会破坏应用程序的生命周期。您还可以控制从应用程序的哪个部分调用哪个操作。

Mohammed,我的安排与你的1,2,3一样。今天的更改是删除我直接调用onResume的部分,并将可能的任务分离到不同的源代码(类和活动文件)中。但是在这个应用程序中没有正在进行的工作,它只是一个1/8秒的活动爆发,只有在用户刷新或更改位置或显示风格时才会重复。例如,如果我要添加年度预测,我同意需要重新设计的解决方案。 - user1644002

0

始终建议使用可维护的代码。

我建议不仅要将您的代码移动到可维护的位置,还要通过遵循任何将表示层与逻辑分离的方法来重构实现。

例如 Android Model View Presenter / MVP with working example

现在,如果您能够阅读更简单的解释(Simpler explanation for MVP),那么易于调试、单元测试和可维护代码的可能性就显而易见了。

针对您提出的问题(@CommonsWare已经解释得很清楚了),将所有600行代码移动到后台线程异步任务中,可以提高应用程序的性能。现在您将不再看到以下类似的消息。

enter image description here

我真的不明白它的问题。

语句引用了可能包含在developer.androidonResume()中的内容

因此,任何在UI线程中运行的方法都应该尽可能少地在该线程上执行工作。特别是,在关键的生命周期方法(如onCreate()和onResume())中,活动应该尽可能少地进行设置。潜在的长时间运行的操作,例如网络或数据库操作,或计算密集型的计算,例如调整位图大小,应该在工作线程中完成(或在数据库操作的情况下,通过异步请求完成)。


Android生命周期方法应该由系统调用,通过调用super onResume / super onPause。这允许系统分配/释放资源。当然,我们可以通过在onResume()onPause()等方法中调用子方法来扩展可用性。但是,在这些方法中保留业务逻辑并调用它们是不可取的。

MVP的情况下,以下是可以遵循的指南。 目前还没有标准/可靠的定义,如何实现相同的功能。但是,我提供的示例是一个很好的起点。

  1. 将视图的代码分离出来。尽可能使视图变得简单
  2. 将所有业务逻辑放在Presenter类中
  3. 模型是负责管理数据的接口

1
Sreehari,我很感激你为我所做的工作。恕我直言,我是一名工程师,在过去的40年里一直都是这样,我信仰规范。操作系统的作者是否有限制使用onResume(恢复)用于您所称的业务逻辑的文件呢?我听说过很多代码行数的提及,但这显然不是资源使用的指标。我可以声明多少字节的内存可以运行多少毫秒。这是一种基于规范的方法。我可以按模块化方式执行,并解决和维护这个问题。谢谢。 - user1644002
在高层次上,我的应用程序在 onResume 调用期间一次创建输出。当有新命令可用时,它会从 onOptionsItemSelected 中的相同例程调用来创建输出。如果我将该创建输出传递给后台例程,它仍然必须从这两个位置之一激活。老实说,对我来说,这是一种非常常见的用户程序方法。它在启动时运行,在响应时运行。否则,它只是在等待中,保持其内存。 - user1644002
同意,没有官方文档。我提供的是基于我拥有的最少经验。让我简要说明一下在onResume中可以做什么。这可能最终解释了什么不应该做 :) (1)事件只是操作系统恢复Activity并且我们可以开始访问应用程序或硬件项目(如蓝牙、相机等)的公告。(2)取消暂停任何在移出Activity之前运行的线程。(3)启动API请求,未收到/完成响应。但仍希望与其他方法同步完成。 - Sreehari
因此,在UI线程上运行的任何方法都应该尽可能少地执行工作。特别是,活动应该在诸如onCreate()和onResume()之类的关键生命周期方法中尽可能少地进行设置。潜在的长时间运行操作,例如网络或数据库操作,或者计算量较大的计算(例如调整位图大小)应在工作线程中完成(或在数据库操作的情况下,通过异步请求完成)。 - Sreehari
很高兴能帮助到你 :) - Sreehari
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接