在 startActivityForResult 返回后,Android parentActivity 没有重新创建

Posted

技术标签:

【中文标题】在 startActivityForResult 返回后,Android parentActivity 没有重新创建【英文标题】:Android parentActivity not getting recreated after startActivityForResult returns 【发布时间】:2015-08-31 09:03:16 【问题描述】:

我有一个 MainActivity ,在其中,我正在加载一个片段 A。从 FragmentA ,我使用 startActivityforResult 调用 google placepicker 活动,如下所示。

PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
Intent intent = builder.build(getActivity());
getActivity().startActivityForResult(intent,PLACE_PICKER_REQUEST);

但是当我选择地点时,onActivityResult(在 FragmentA 或 MainActivity 中)没有被调用。事实上,我的应用程序在 startActivityForResult 调用后被破坏了。

根据我的理解,如果调用活动在内存中不可用,android 应该重新创建它。但它没有发生。即使 onCreate 也没有在 MainActivity 中被调用。

谁能告诉我这种行为背后的原因还是我错过了什么?

现在我尝试在同一个应用程序中使用另一个 Activity,而不是 PlacePicker Activity。

假设我加载了MainActivityFragmentA。我正在从FragmentA 调用SubActivitystartActivityForResult。现在从SubActivity 返回时,应用程序退出。我在我的设备中启用了Dont keep activities 来测试这个特定的场景。当我移动到SubActivity 时,我可以看到MainActivity 被破坏。但是从SubActivity 返回时,android 不会重新创建MainActivity(甚至onCreate 也没有被调用。应用程序只是退出)。

【问题讨论】:

“我的应用程序在 startActivityForResult 调用后被破坏了。” 你的意思是它正在崩溃吗? 您是否在清单文件中提供了意图过滤器?这是重新创建您的活动所必需的 @codeMagic 它没有崩溃。调用 startActivityForResult 后,我​​可以看到 MainActivity 中的 onDestory 方法被调用。当我从启动的活动中选择一个位置时,它会返回。但我的应用程序没有重新创建。如果我选择的地方足够快(在我的 MainActivity 被破坏之前),onActivityResult 会被正确调用。 您是否在某处为该活动致电finish()?也许在onActivityResult() 或其他地方? 我在我的一个应用程序中遇到了相同的情况,并且启用了“不保留活动”后,一旦启动的 Activity 返回,我的 Activity 将被销毁并重新创建。这是预期的行为,因此您的应用程序或设备有一些不寻常的地方。如果没有更多代码(包括清单)和有关您的设备/操作系统的详细信息,我无法回答这个问题。 【参考方案1】:

发生这种情况的原因有很多,但希望这种情况很少发生。 如果需要回收资源,操作系统会在后台销毁 Activity,这更可能发生在内存和处理能力较低的设备上。

使用Do not keep Activities 设置是测试这种情况的好方法,在这种情况下,即使重新创建了 Activity/Fragment 也会出现其他问题。启用此设置后,显示 PlacePicker 时 Activity 和 Fragment 确实会被销毁,然后当 onActivityResult() 进入时,没有有效的 Context 因为 Activity 和 Fragment 仍在重新创建过程中。

我在禁用设置的情况下执行受控测试后发现了这一点,然后启用了设置,然后查看结果

我将日志记录在 Activity 和 Fragment 的每个生命周期回调中,以便了解这些调用期间发生了什么。

这是我使用的包含 Activity 和 Fragment 的完整类:

public class MainActivity extends AppCompatActivity 

    MyFragment myFrag;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        Log.d("PlacePickerTest", "Activity onCreate");

        myFrag = new MyFragment();

        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) 
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, myFrag)
                    .commit();
        
    

    @Override
    protected void onResume() 
        super.onResume();

        Log.d("PlacePickerTest", "Activity onResume");
    

    @Override
    protected void onPause() 
        super.onPause();

        Log.d("PlacePickerTest", "Activity onPause");
    

    @Override
    protected void onDestroy() 
        Log.d("PlacePickerTest", "Activity onDestroy");
        super.onDestroy();
    

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    

    @Override
    public void onActivityResult (int requestCode, int resultCode, Intent data) 
        super.onActivityResult(requestCode, resultCode, data);

        Log.d("PlacePickerTest", "Activity onActivityResult requestCode:" + requestCode);

        if (requestCode == 199)
            //process result of PlacePicker in the Fragment
            myFrag.processActivityResult(data);
        
    

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        int id = item.getItemId();
        if (id == R.id.action_settings) 
            //open PlacePicker from menu item
            myFrag.startPlacePicker();
            return true;
        

        return super.onOptionsItemSelected(item);
    

    /**
     * Fragment containing a map and PlacePicker functionality
     */
    public static class MyFragment extends Fragment 

        private GoogleMap mMap;
        Marker marker;

        public MyFragment() 
        

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) 
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            Log.d("PlacePickerTest", "Fragment onCreateView");

            return rootView;
        


        @Override
        public void onResume() 
            super.onResume();

            Log.d("PlacePickerTest", "Fragment onResume");

            setUpMapIfNeeded();
        

        @Override
          public void onPause() 
            super.onPause();

            Log.d("PlacePickerTest", "Fragment onPause");
        

        @Override
        public void onDestroy() 
            Log.d("PlacePickerTest", "Fragment onDestroy");
            super.onDestroy();
        

        private void setUpMapIfNeeded() 
            // Do a null check to confirm that we have not already instantiated the map.
            if (mMap == null) 
                // Try to obtain the map from the SupportMapFragment.
                mMap = ((SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map))
                        .getMap();
                // Check if we were successful in obtaining the map.
                if (mMap != null) 
                    setUpMap();
                
            
        

        private void setUpMap() 

            // Enable MyLocation Layer of Google Map
            mMap.setMyLocationEnabled(true);
            mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
            mMap.getUiSettings().setZoomControlsEnabled(true);
            mMap.getUiSettings().setMyLocationButtonEnabled(true);
            mMap.getUiSettings().setCompassEnabled(true);
            mMap.getUiSettings().setRotateGesturesEnabled(true);
            mMap.getUiSettings().setZoomGesturesEnabled(true);

        

        public void startPlacePicker()
            int PLACE_PICKER_REQUEST = 199;
            PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
            //Context context = getActivity();
            try 
                Log.d("PlacePickerTest", "Fragment startActivityForResult");
                getActivity().startActivityForResult(builder.build(getActivity()), PLACE_PICKER_REQUEST);
             catch (GooglePlayServicesRepairableException e) 
                e.printStackTrace();
             catch (GooglePlayServicesNotAvailableException e) 
                e.printStackTrace();
            
        

        public void processActivityResult ( Intent data) 

            if (getActivity() == null) return;

            Log.d("PlacePickerTest", "Fragment processActivityResult");


            //process Intent......
            Place place = PlacePicker.getPlace(data, getActivity());
            String placeName = String.format("Place: %s", place.getName());
            String placeAddress =  String.format("Address: %s", place.getAddress());

            LatLng toLatLng = place.getLatLng();

            // Show the place location in Google Map
            mMap.moveCamera(CameraUpdateFactory.newLatLng(toLatLng));
            mMap.animateCamera(CameraUpdateFactory.zoomTo(15));

            if (marker != null) 
                marker.remove();
            
            marker = mMap.addMarker(new MarkerOptions().position(toLatLng)
                    .title(placeName).snippet(placeAddress)
                    .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));

        
    

以下是生成的日志,可以深入了解在正常情况下在该过程中调用了哪些生命周期回调:

 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume
 D/PlacePickerTest﹕ Fragment startActivityForResult
 D/PlacePickerTest﹕ Fragment onPause
 D/PlacePickerTest﹕ Activity onPause
 D/PlacePickerTest﹕ Activity onActivityResult requestCode:199
 D/PlacePickerTest﹕ Fragment processActivityResult
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume

因此,如您所见,onDestroy() 从未被调用,onPause()onResume() 在 Activity 和 Fragment 上都被调用。

这是视觉上的结果:

然后选好地方后:

然后,我在“设置”的“开发人员选项”下启用了Do not keep Activities,并运行了相同的测试。

这些是生成的日志:

 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume
 D/PlacePickerTest﹕ Fragment startActivityForResult
 D/PlacePickerTest﹕ Fragment onPause
 D/PlacePickerTest﹕ Activity onPause
 D/PlacePickerTest﹕ Activity onDestroy
 D/PlacePickerTest﹕ Fragment onDestroy
 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onActivityResult requestCode:199
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume

所以,当显示 PlacePicker 时,您可以看到 Activity 和 Fragment 都被销毁,并且在 PlacePicker 中选择了地点后,代码执行从未到达Fragment processActivityResult 日志条目,并且应用程序从未显示在地图上选择的地点。

那是因为空上下文检查:

 if (getActivity() == null) return;

 Log.d("PlacePickerTest", "Fragment processActivityResult");

 //process Intent......
 Place place = PlacePicker.getPlace(data, getActivity());

所以,确实调用了onActivityResult(),但它是在重新创建 Activity 和 Fragment 的同时进行的,您需要一个有效的上下文来调用 PlacePicker.getPlace(data, getActivity());

好消息是大多数最终用户不会启用Do not keep Activities 设置,并且大多数情况下您的活动不会被操作系统破坏。

【讨论】:

如果操作系统删除了调用 startActivityForResult 的活动,就会发生这种情况。 @JawadLeWywadi 你说得对,再读一遍我意识到这个答案有一些问题。刚刚编辑,我觉得现在好多了。谢谢! 这应该是正确的答案,尤其是对于设置“不要让活动保持活动状态”的提示......【参考方案2】:

Android 以您描述的方式清理活动似乎很不寻常,但如果是这种情况,那么您的活动仍应恢复。 Android 不应销毁 Activity,除非您专门调用 finish() 或某些原因导致 Activity 过早结束。

如果参考活动生命周期图:

在您描述的场景中,第一个活动应该调用 onStop,而不是 onDestroy,然后当您从第二个活动返回时,它应该再次调用 onStart。

我创建了一个非常简单的应用程序来测试您描述的场景,其中包含以下内容:

有 2 个活动,FirstActivity 和 SecondActivity FirstActivity 有一个按钮,当单击该按钮时,它会以startActivityForResult() 启动 SecondActivity 在自定义应用程序类中使用 ActivityLifecycleCallbacks 记录活动生命周期事件 在 FirstActivity 中,onActivityResult 在被调用时额外输出到日志

这是输出:

应用程序已启动(FirstActivity 已创建并启动且可见):

FirstActivity onCreate
FirstActivity onStart
FirstActivity onResume

我按下按钮启动SecondActivity:

FirstActivity onPause
SecondActivity onCreate
SecondActivity onStart
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop

注意,onDestroy 不会被调用。

现在我按下返回按钮并返回第一个活动:

SecondActivity onPause
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
SecondActivity onStop
SecondActivity onDestroy

后退按钮在 SecondActivity 上调用 finish,所以它被销毁了

现在,如果我再次按下返回,FirstActivity 也将完成,导致调用 onDestroy

FirstActivity onPause
FirstActivity onStop
FirstActivity onDestroy

你可以看到这个例子完全符合生命周期图。活动只有在按下后退按钮后才会被销毁,这会导致活动调用finish()

您提到您尝试在开发者选项中打开“不保留活动”,我们可以重复上述启用此选项的实验,看看会发生什么。我刚刚添加了相关的生命周期事件以节省重复上述所有内容:

在第一个活动中按下按钮启动第二个活动后:

...
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop
FirstActivity onDestroy

正如所料,这次活动被销毁了。 当您再次导航回第一个活动时会发生这种情况:

SecondActivity onPause
FirstActivity onCreate
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
...

这一次 onCreate 被再次调用,因为系统没有第一个活动的停止版本要重新启动。此外,onActivityResult() 仍然被调用,尽管必须重新创建活动。

这进一步支持了您的第一个活动中的某些内容必须调用finish() 或导致它崩溃。但是,如果没有看到您的实际代码,这是推测。

最后,如果您的 Activity 出于某种原因需要重新创建,为了保持状态,您可以覆盖 onSaveInstanceState() 并将任何状态信息添加到包中:

protected void onSaveInstanceState(Bundle outState) 
    super.onSaveInstanceState(outState);
    outState.putString(MY_STRING_KEY, "my string value");

重新创建 Activity 后,您会在 onCreate 中重新获得一个包,其中应该包含您保存的所有内容:

protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    ...
    if (savedInstanceState != null) 
        // Restore previous state
    

【讨论】:

我正在测试重新创建活动的相同情况(请参阅我的编辑)。问题是即使返回时也没有调用 onCreate 方法。 为什么这是公认的答案?它没有回答 imo 的问题。

以上是关于在 startActivityForResult 返回后,Android parentActivity 没有重新创建的主要内容,如果未能解决你的问题,请参考以下文章

Android Fragment 中的 startActivityForResult() 与 getActivity().startActivityForResult()

如何在 RecyclerView 中实现 StartActivityForResult

在视图中实现方法 startActivityForResult

Fragment的startActivityForResult和Activity的startActivityForResult的区别

在adapter中startactivityforresult

startActivityForResult() 在内存不足的情况下