Android应用中使用Fragment组件的一些问题及解决方案总结

Fragment的主要意义就是提供与Activity绑定的生命周期回调。
Fragment不一定要向Activity的视图层级中添加View. 当某个模块需要获得Activity的生命周期回调的时候,就可以考虑通过Fragment来实现.
例如: DialogFragment, 调用show方法来显示一个Dialog(这个一个子Window,并不在Activity的视图层级中),当旋屏时,DialogFragment利用onDestroyView回调来dismiss Dialog,然后Activity重建之后,DialogFragment利用onStart回调再显示Dialog。
当然,我们也可以创建一个完全没有UI的Fragment,比如BackgroundWorkerFragment,在onResume的时候执行一个Task,在onPause的时候暂停一个Task。

Fragment 生命周期
先来回顾一下基础知识,Fragment的生命周期图如下:

说明:总的来说,Fragment和Activity的生命周期类似。需要注意的是,它相比于Activity,多了onAttach(), onDetch(), onCreateView()和onDestroyView()这几个回调函数;但是,却少了onRestart()。
Fragment的生命周期非常复杂,分为以下几种情况:

  • 如果是通过XML中的<fragment/>标签实例化的,那么第一个收到的回调将是onInflate
  • 如果setRetainInstance(true),那么当Activity重建时,Fragment的onDestroy以及Activity重建后Fragment的onCreate回调不会被调用.(无论是否将其添加到了返回栈)
  • 如果当前显示的是Fragment A,然后执行FragmentTransaction.replace(),那么Fragment A会执行onPause()->onStop()->onDestroyView()->onDestroy()->onDetach(),如果执行FragmentTransaction.replace().addToBackStack(),那么Fragment A会执行onPause()->onStop()->onDestroyView()
  • FragmentTransaction.hide(),将不会导致onPause(),而是会触发onHiddenChanged()
  • FragmentTransaction.detach(),会导致onPause()->onStop()->onDestroyView(),注意:onDestroy()和onDetach()不会调用

FragmentTransaction

  • 对于Fragment的操作都是通过FragmentTransaction来进行的,一个FragmentTransaction可以包含一个或者多个操作,通过commit或者commitAllowingStateLoss来提交.如果该FragmentTransaction被加入返回栈,那么出栈的时候,该Transaction中的所有操作都会被撤销
  • commit方法是异步的(handler post相应的message到MainLooper关联的Message queue),如果需要立刻执行Transaction的操作,可以调用executePendingTransactions()
  • FragmentTransaction的commit方法以及FragmentManager的popBackStack方法都是异步的,给调用者带来了很多不便,虽然可以通过调用executePendingTransactions()方法来立即执行,但是为什么默认是异步的呢??(我觉得是因为:提交一个Transaction,会导致Fragment的生命周期方法的执行,甚至是多个回调的执行,如果Fragment在这些回调中又提交新的Transaction,那么可能会破坏当前Transaction的状态,比方说这是一个pop操作)

Can not perform this action after onSaveInstanceState

在使用Fragment的过程中,常常会遇到在Activity的onSaveInstanceState方法调用之后,操作commit或者popBackStack而导致的crash.
因为在onSaveInstanceState方法之后的操作状态可能会丢失,因此Android framework默认会抛出一个异常.
对于commit方法来说,单纯避免这个异常很简单,使用commitAllowingStateLoss方法即可.但是popBackStack以及popBackStackImmediate也都会检查state(checkStateLoss),特别需要注意的是Activity的onBackPressed方法

public void onBackPressed() {
  if (!mFragments.popBackStackImmediate()) {//注意
    supportFinishAfterTransition();
  }
}

如果onBackPressed在onSavedInstanceState之后调用,那么就会crash.
onBackPressed的调用时机:

* targetSdkVersion <= 5,在onKeyDown中调用
* targetSdkVersion > 5,在onKeyUp中调用
onSavedInstanceState的调用时机(如果调用的话):

* 一定在onStop之前
* 可能在onPause之前,也可能在onPause与onStop之间
需要注意的是: onSavedInstanceState方法不一定会调用,只有在Activity因为某些原因而被Framework销毁,并且之后还需要重新创建的情况,才需要调用(例如:旋屏,或者内存不足而回收返回栈中的某些Activity)

举例:
* Activity A在前台时,屏幕逐渐变暗直至锁屏,那么A的onSavedInstanceState会被调用
* Activity A start Activity B,Activity A的onSavedInstanceState会被调用
* Activity A因为返回键或者finish调用而返回到上一个界面,那么A的onSavedInstanceState不会被调用
因此,当onBackPressed在onSavedInstanceState方法之后调用,就一定会crash.解决方法主要有两种:

重写Activity的onSavedInstanceState()方法,并且注释掉super调用.
这种方法能避免crash,但是它会导致整个Activity的状态丢失.以DialogFragment为例,正常情况下,显示的DialogFragment在旋屏Activity重新创建之后,不需要我们处理,Dialog会自动显示出来(参见DialogFragment.onStart()),但是注释掉Activity的onSavedInstanceState()方法之后,Fragment状态丢失,Activity重新创建之后,Dialog也就不会再显示出来了.

更好且通用的做法:在调用commit,popBackStack以及onBackPressed方法之前,判断onSavedInstanceState()方法是否已经执行,并且onResume方法还没有执行,如果不是,那么直接操作,否则加入到pending队列,等待onResumeFragments或者onPostResume之后再执行.

注意:不要在onResume中操作,因为这时候FragmentManager中的mStateSaved依然可能是true.(如果执行顺序是onSavedInstanceState()->onPause()->onResume() 或者 onPause()->onSavedInstanceState()->onResume())

例如:

public void onDataReceived() {
  if(isStateSaved()) {//isStateSaved()由BaseActivity提供
    addPendingFragmentOperation(new Runnable() {
      @Override
      public void run() {
        getSupportFragmentManager().popBackStackImmediate();
      }
    });
  } else {
    getSupportFragmentManager().popBackStackImmediate();
  }
}

@Override
protected void onPostResume() {
  super.onPostResume();
  if(pendingFragmentOperation != null && !pendingFragmentOperation.isEmpty()) {
    for(Runnable operation : pendingFragmentOperation) {
      operation.run();
    }
    pendingFragmentOperation.clear();
  }
}

startActivityForResult

requestCode的可用区间:

1.Activity: [Integer.MIN_VALUE, Integer.MAX_VALUE]
(1)当requestCode取值在[Integer.MIN_VALUE, -1]区间中,效果和startActivity()一样,不会收到onActivityResult()回调
(2)内置的Fragment可用requestCode的区间和Activity相同
2.support库: Fragment,以及FragmentActivity:[-1, 65535]
(1)requestCode == -1,效果和startActivity()一样,不会收到onActivityResult()回调
(2)requestCode 在 [Integer.MIN_VALUE, -2]或者[65536, Integer.MAX_VALUE]之间,会抛出异常(requestCode只能使用低16比特)
建议: requestCode的取值统一限制在[-1, 65535]之间

嵌套Fragment

首先要说的是尽量不要使用嵌套Fragment.
当在嵌套Fragment中使用startActivityForResult()时,会遇到的问题:

所有的Fragment都收不到onActivityResult()
某个level 1 的Fragment收到了onActivityResult()
总之那个发起startActivityForResult()的嵌套Fragment是一定不会收到onActivityResult()回调的.

原因如下:(可参考上面说的requestCode)
FragmentActivity.startActivityFromFragment()会改动requestCode,用高16比特存储Fragment在FragmentManager中的index,而低16比特作为Fragment可用的requestCode.在FragmentActivity.onActivityResult()中,根据高16比特,从FragmentManager中找到对应的Fragment,然后将低16比特的值作为requestCode,调用Fragment.onActivityResult().

那么requestCode中只能存储一个index,即root FragmentManager中的Fragment index.因此就会出现上面所列出的情形:

  • 当嵌套Fragment在childFragmentManager中的index,大于rootFragmentManager中的所有index时, rootFragmentManager将找不到与此index对应的Fragment,所以没有Fragment能收到onActivityResult()
  • 当嵌套Fragment在childFragmentManager中的index,小于等于rootFragmentManager中的所有index时,那么隶属于rootFragmentManager的一个Fragment将会收到onActivityResult()
  • 总之即使能有Fragment能收到onActivityResult(),那也是顶层的某个Fragment,而不是发起请求的嵌套Fragment

解决方案:

  • 不使用嵌套Fragment :)
  • 依然利用requestCode,将其低16位拆分,其中的高8位用来存储childFragmentManager中的index,低8位留给ChildFragment使用.(如果嵌套层级不深,那么此方案还是不错的,如果层级较深,那么留给Fragment的requestCode的可用值区间将非常局限)
  • Android 4.2(Api 17)以后,可以使用内置的Fragment,以及ChildFragmentManager,内置Fragment不再需要借助requestCode的高16比特来记录它的index.而是由Framework收到Fragment.startActivityForResult()时,记录该Fragment的标识(android:fragment:${parentIndex}:${myIndex}),派发result时,就根据这个标识找到那个Fragment.因此就不会出现ChildFragment收不到onActivityResult()回调的问题了.可以参考Activity.dispatchActivityResult()

Tips

开发的时候,可以打开Fragment相关的调试信息

FragmentManager.enableDebugLogging(BuildConfig.DEBUG);
Activity的onResume被调用时,Fragment的onResume还未被调用.
protected void onPostResume() {
  super.onPostResume();
  mHandler.removeMessages(MSG_RESUME_PENDING);
  onResumeFragments();
  mFragments.execPendingActions();
}
protected void onResumeFragments() {
  mFragments.dispatchResume();
}

如果需要在Fragment的onResume都执行完后再执行某个操作,可以重写onPostResume()方法,一定要调用 super.onPostResume()

1.IllegalStateException(Fragment not attached to Activity)的问题
这个异常通常的发生情况是:在Fragment中启动一个异步任务,然后在回调中执行和resource相关的操作(getString(...)),或者startActivity(...)之类的操作.但是这个时候Fragment可能已经被detach了,所以它的mHost==null,因此在执行这些操作之前,需要先判断一下isAdded().
注意: 这里不要使用isDetached()来判断,因为Fragment被detach之后,它的isDetached()方法依然可能返回false

2.如果Fragment A是因为被replace而detach的,那么它的isDetached()将返回false
3.如果Fragment A对应的FragmentTransaction被加入到返回栈中,因为出栈而detach,那么它的isDetached()将返回true

final public Resources getResources() {
  if (mHost == null) {
    throw new IllegalStateException("Fragment " + this + " not attached to Activity");
  }
  return mHost.getContext().getResources();
}
public void startActivity(Intent intent, @Nullable Bundle options) {
  if (mHost == null) {
    throw new IllegalStateException("Fragment " + this + " not attached to Activity");
  }
  mHost.onStartActivityFromFragment(this /*fragment*/, intent, -1, options);
}
时间: 2016-05-10

Android应用开发中使用GridView网格布局的代码示例

基本布局演示 1. 定义包含GridView 的 main.xmk <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fi

Android应用 坐标系详细介绍

Android 应用坐标系详解: 由于最近做Android项目需要用坐标系的知识,所以度娘了一下,整理了相关资料,记录下来. 1 背景 去年有很多人私信告诉我让说说自定义控件,其实通观网络上的很多博客都在讲各种自定义控件,但是大多数都是授之以鱼,却很少有较为系统性授之于渔的文章,同时由于自己也迟迟没有时间规划这一系列文章,最近想将这一系列文章重新提起来,所以就来先总结一下自定义控件的一个核心知识点--坐标系. 很多人可能不屑一顾Android的坐标系,但是如果你想彻底学会自定义控件,我想说了解A

第1个Android应用程序 Android制作简单单页导航

本例子演示如何添加一个简单的单页导航,在此基础上,再演示如何在第2个页面中显示第1个页面中拨打过的所有电话号码. (1)通过该例子理解Android App的基本架构. (2)通过该例子理解实现Android多屏幕导航的基本技术. 本例子只是为了让我们对Android App开发有一个较全面的感性认识,读者不必一开始就纠结于代码中的细节问题,涉及到的相关概念在后面还会分别介绍. 运行截图 运行截图(Api19.Api21.Api23的实现代码都相同): 界面操作 单击"将文本转换为数字"

Android应用中实现手势控制图片缩放的完全攻略

一.概述 现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位~~~ 我相信看图的整个步骤,大家或者说用户应该不希望被打断把~~~"我擦,竟然不能放大,什么玩意,卸了~~" , "我擦,竟然不能移动,留有何用,卸了~~". 哈~所以对于图片的预览,一来,我们要让用户爽:二来,我们作为开发者,也得知道如何实现~~~ 想要做到图片支持多点触控,自由的进行缩放.平移,需要了解几个

详解Android应用中DialogFragment的基本用法

DialogFragment的基本用法 1. 创建DialogFragment public class DialogA extends DialogFragment implements DialogInterface.OnClickListener { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder

Docker 实现浏览器里开发Android应用的功能

在浏览器里开发Android应用          这里需要用到Docker的知识, Che 发布后对Android应用开发多了一个工具,这里就对如何实现该功能就行详细介绍: Eclipse Che 最近Che发布了正式版,那我就介绍下在Che上开发Android吧-- 使用Che需要懂得一些Docker的知识,只需要一点点即可,因为Che是基于Docker的,所以了解Docker有助于理解Che的工作方式. 不废话,教程只有四步,开始. 第一步:部署docker服务器 我选择的是digital

Android应用开发中WebView的常用方法笔记整理

基本使用 使用WebView通常是需要网络的,所以需要加上访问网络的权限 <uses-permission android:name="android.permission.INTERNET" /> 1.加载某个url的方法 WebView.loadUrl("http://www.baidu.com"); 需要注意的是不要省略前面的http://,省略的话,某些ROM中的WebView会加载失败 2.加载assets中的HTML WebView.load

非常实用的小功能 Android应用版本的更新实例

每一个应用都是具备一个功能,那就是版本更新,我记得我之前在面试的时候,面试官让我介绍一下应用版本更新的一些具体操作.我当时因为做过这个功能,所以回答的还是很流畅,现在我把这个分享给大家,需要能够共同进步. 我当时是这么说的: 首先呢,我们是应该在用户登录后,在首页执行检查版本信息的操作,具体是,获取到本地的版本号后,提交给服务器进行判断,然后后台来告诉我们当前版本是否为最新版本,紧接着我们拿到下载地址,执行下载的操作,具体的可以使用输入输出流来对文件进行存储和读取,为了方便下载,我们还可以使用一

Android应用中设置alpha值来制作透明与渐变效果的实例

Android系统支持的颜色是由4个值组成的,前3个为RGB,也就是我们常说的三原色(红.绿.蓝),最后一个值是A,也就是Alpha.这4个值都在0~255之间.颜色值越小,表示该颜色越淡,颜色值越大,表示该颜色越深.如果RGB都是0,就是黑色,如果都为255,就是白色.Alpha也需要在0~255之间变化.Alpha的值越小,颜色就越透明,Alpha的值越大,颜色就不透明.当Alpha的值为0时,颜色完全透明,完全透明的位图或者图形从View上消失.当Alpha的值为255时,颜色不透明.从A

Android应用自动更新功能实现的方法

本文给大家分享Android里应用版本更新功能这一块的实现. 一个好的应用软件都是需要好的维护,从初出版本到最后精品,这个过程需要版本不停的更新,那么如何让用户第一时间获取最新的应用安装包呢?那么就要求我们从第一个版本就要实现升级模块这一功能. 自动更新功能的实现原理,就是我们事先和后台协商好一个接口,我们在应用的主Activity里,去访问这个接口,如果需要更新,后台会返回一些数据(比如,提示语:最新版本的url等).然后我们给出提示框,用户点击开始下载,下载完成开始覆盖安装程序,这样用户的应

Android 软件自动更新功能实现的方法

相信所有的用户都遇到过软件提醒更新的情况,下面就将实现此功能 首先看一下程序目录结构    步骤: 1.新建一个类UpdateManger,用于显示提示更新 复制代码 代码如下: public class UpdateManger { // 应用程序Context private Context mContext; // 提示消息 private String updateMsg = "有最新的软件包,请下载!"; // 下载安装包的网络路径 private String apkUrl

Android程序自动更新功能模块的实现方法【附完整demo源码下载】

本文实例讲述了Android程序自动更新功能模块的实现方法.分享给大家供大家参考,具体如下: 在程序启动的时候检测服务器上有没有对应版本更新,如果有更新,提示用户是否更新. 在程序启动的时候首先调用更新模块检测服务器上存放的版本号跟当前程序的版本号如果大于当前版本号,弹出更新对话框,如果用户选择更新,则显示当前更新状态,然后替换当前程序. 程序调用版本更新检测: private UpdateManager updateMan; private ProgressDialog updateProgr

Android实现APP自动更新功能

现在一般的android软件都是需要不断更新的,当你打开某个app的时候,如果有新的版本,它会提示你有新版本需要更新.该小程序实现的就是这个功能. 该小程序的特点是,当有更新时,会弹出一个提示框,点击确定,则在通知来创建一个进度条进行下载,点击取消,则取消更新. 以下是详细代码: 1.创建布局文件notification_item.xml,用于在通知栏生成一个进度条和下载图标. <?xml version="1.0" encoding="utf-8"?>

基于Retrofit2+RxJava2实现Android App自动更新

本文实例为大家分享了Retrofit2 RxJava2实现Android App自动更新,具体内容如下 功能解析 自动更新可以说已经是App的标配了,很多第三方平台也都支持这个功能,最近手头上的项目需要加入这个App自动更新,考虑到项目里有用到Retrofit2和RxJava2,于是打算使用它俩自己实现这个功能. 分析App自动更新,可以分为以下三个功能点: 1.APK文件的下载 2.下载进度的实时更新显示 3.下载完成后的自动安装 其中比较难的一点是下载进度的实时更新显示,更难的是如何优雅的进

浅谈Android Studio3.6 更新功能

前言 下载google CodeLab的程序时,提示要更新3.6版本才能运行程序,于是更新了一下,看看有什么新功能. 界面设计工具 这次更新了一些设计工具,比如Layout Editor 和 Resource Manager. 现在,在XML或设计工具的颜色选择器中,Android Studio会在您的应用程序中填充颜色资源,以便您快速选择和替换颜色资源值. 拆分视图并放大设计编辑器 设计编辑器(例如,布局编辑器和导航编辑器)现在提供一个拆分视图,使您可以同时查看UI的"设计"视图和&

php自动更新版权信息显示的方法

本文实例讲述了php自动更新版权信息显示的方法.分享给大家供大家参考.具体分析如下: 我们一般会在页面下方输出版权信息,包含年份信息,每年都要修改,这段简单的代码帮你解决这个问题,自动更新年份 function autoUpdatingCopyright($startYear){ // given start year (e.g. 2004) $startYear = intval($startYear); // current year (e.g. 2007) $year = intval(d

Android App自动更新之通知栏下载

本文实例为大家分享了Android App自动更新通知栏下载的具体代码,供大家参考,具体内容如下 版本更新说明 这里有调用UpdateService启动服务检查下载安装包等 1. 文件下载,下完后写入到sdcard 2. 如何在通知栏上显示下载进度 3. 下载完毕自动安装 4. 如何判断是否有新版本 版本更新的主类 package com.wei.update; import java.io.IOException; import java.io.InputStream; import java

Android中实现ping功能的多种方法详解

使用java来实现ping功能. 并写入文件.为了使用java来实现ping的功能,有人推荐使用java的 Runtime.exec()方法来直接调用系统的Ping命令,也有人完成了纯Java实现Ping的程序,使用的是Java的NIO包(native io, 高效IO包).但是设备检测只是想测试一个远程主机是否可用.所以,可以使用以下三种方式来实现: 1. Jdk1.5的InetAddresss方式 自从Java 1.5,java.net包中就实现了ICMP ping的功能. 使用时应注意,如

Android应用APP自动更新功能的代码实现

由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此我们有必要给我们的Android应用增加自动更新的功能. 既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息: <update> <version>2</version> <name>baidu