Activity与Fragment易混点归纳
来源:互联网 发布:世界网络强国有哪些 编辑:程序博客网 时间:2024/04/28 08:57
Android开发中Activity和Fragment是非常重要的两个知识点,这里我们就分别归纳一下,在使用Activity和Fragment时需要注意的一些事项,特别是对于Fragment,在对其进行管理时我们要选择好的一些方式。
一、Activity要点
Activity负责展示UI给用户,负责和用户的交互操作。本部分主要对Activity的生命周期、如何保存状态信息、如何讲一个Activity设置成窗口模式、启动模式和完全退出Activity的方式,五部分进行总结。
1、生命周期
Android中是通过Activity栈来管理创建出的Activity,当一个新的Activity被启动时,这个新的Activity就会被加入到Activity栈的栈顶,同时呈现到用户前台,供用户交互,原来的栈顶元素被压入到第二位置,但是它的状态信息还保存着。
掌握Activity生命周期,重点是理解官方给出的生命周期图:
当我们通过context.startActivity()启动一个新的Activity,这个新的Activity被压入到Activity栈顶,来到了屏幕的前台,开始一个完整的Activity生命周期。
--onCreate():只在Activity被刚创建时执行,在onCreate方法中我们初始化我们的Activity,比如,加载布局文件,获取控件(findViewById),绑定触摸事件与用户进行操作等。一般情况下,Activity生命周期的切换不会再触发这个方法的执行,只有当系统极度缺乏内存资源,并且这个Activity没有处在用户前台时,此时该Activity可能被杀死,当再次回到这个Activity,因为这个Activity已被杀死,此时就需要重新创建(就相当于重新startActivity()了),因此会再次进入onCreate进行初始化工作。
--onStart()、onResume():在加载完布局后,系统执行一些内部的启动操作,执行到onResume时,用户可以看到完整的UI界面了,此时Activity处于运行状态。
--onPause():当前的Activity失去了焦点,但依然可以看见,比如当我们点击了一个对话框出来、打开了一个非全屏的Activity、打开了一个透明的Activity,此时原来的Activity就会进入onPause()方法,它依然持有状态信息和内存资源,只有当系统极度缺乏内存资源时,才会杀死处于onPause状态的Activity。
--onStop():当一个Activity被另一个Activity完全覆盖的时候,对用户来说这个Activity不可见了,此时这个Activity就进入onPause状态,它也依然保存着状态信息和资源,但是容易被系统杀死,当内存不是那么充足的时候。
--onDestory():当Activity处于onPause和onStop状态时,系统可能因系统资源吃紧会杀死该Activity,在系统回收该Activity之前,会调用onDestory()方法,在里面进行一些资源的释放工作。onDestory()的调用,可能是用户主动的行为,也可能是因系统资源不足系统需要回收该Activity,在回收前调用。
在Activity被杀死的情况下,当这个Activity再次回到用户前台时,需要重新初始化,即再次进入onCreate,如上图左边的环形图。
在Activity没有被杀死的情况下,处于onPause和onStop状态的Activity再次回到前台时,需要系统还原一些状态,对于onPause状态,由于它处于"比较活跃的一种状态",只需要进入到onResume中由系统设置一些信息即可重新回到前台,对于onStop状态,因为它处于很有可能被销毁的一种状态,部分资源可能丢失,需要先进入onRestart(),然后再次进入onStart()方法,进行回到前台的准备工作。
Thevisible lifetime(可见的生命周期) :从onStart()到进入onStop()前(即onStart-->onPause),这个Activity都可被用户看见,这期间,不一定处于前台,也不一定能够供用户交互(比如处于onPause状态时)
Theforeground lifetime(前台生命周期):从onResume()到进入onPause前,这个期间,Activity处于可以和用户交互时期。这个时期,可能也会频繁在onResume和onPause状态间切换。
2、保存状态信息(推荐在onPause中保存)
从前面生命周期中可以看到,当Activity处于onPause和onStop状态时都有可能被系统回收,因此对于我们该Activity中的一些关键数据最好能够保存下来,当用户再次进入时能够回到原来的状态。官方文档中是推荐我们在onPause()中区保存信息的,比如将数据写入到SharedPreference中。Android还提供了onSaveInstanceState(Bundle)方法来保存数据,通过Bundle来持久化数据。如下例子:
@Override当我们这个Activity被销毁而重新创建re-created的时候,通过onCreate(Bundle)中的参数获取到该信息,如下:
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("name","lly");
}
@Override注意:onSaveInstanceState()方法只是在Activity“很容易被销毁的时候调用”,它并不是Activity的生命周期方法,这个调用时机是不确定的,对于点击返回按钮这种主动行为不会去调用这个方法。网上很多说在按下HOME键和旋转屏幕的时候会去调用,但是经过我测试一下,发现不管是我按下HOME键还是旋转屏幕,这个方法都没有被调用。按我理解,这个方法只有在“很容易被销毁的时候调用”,这个尺度应该是系统根据手机具体内存资源情况决定是否调用。因此官方文档中推荐在onPause中进行数据信息的保存操作。(Note that it is important to save persistent data in
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState != null){
name = savedInstanceState.getString("name");
}
}
onPause()
instead of onSaveInstanceState(Bundle)
because the latter is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.)ononSaveInstanceState()方法在Activity、Fragment、Service、各类View中都有提供。其实,默认的ononSaveInstanceState、onRestoreInstanceState()方法的默认实现中,就帮我们保存了系统的一些状态信息,如果我们要保存自己的一些状态信息,就需要重写上面的方法。特别地,在View中,使用ononSaveInstanceState时,对应的这个View一定要在布局文件中定义id,否则没有id,是不会进入到ononSaveInstanceState方法中的。
3、如何将一个Activity设置成窗口的样式
1.在res/values文件夹下的style.xml文件中加入如下代码:
- <style name= "Theme.FloatActivity" parent= "android:style/Theme.Dialog" >
- <!-- float_box为我们定义的窗口背景 ,这个不是必须的-->
- <item name="android:windowBackground" > @drawable /float_box</item>
- </style>
2.在res/drawable文件夹下新建float_box.xml文件,代码如下:
- <?xml version= "1.0" encoding= "utf-8" ?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android" >
- <solid android:color="#ffffff" />
- <stroke android:width="3dp" android:color= "#ff000000" />
- <corners android:radius="3dp" />
- <padding android:left="10dp" android:top= "10dp" android:right= "10dp" android:bottom= "10dp" />
- </shape>
3.在AndroidMainifest.xml中Activity的声明中加入
android:theme= "@style/Theme.FloatActivity"
效果如图:
图中显示了一个activity启动另一个activity的效果,布局文件是同一个。其中被启动的activity2是以对话框样式显示,不完全覆盖住启动它的activit1,类似alertDialog。
这与普通的activity不同,默认情况下,activity2会完全遮住activity1,启动activity2后,会调用activity1的onStop方法,而这种以对话框样式显示的activity不会,此时调用的是onPause()。(详见Activity的生命周期)
4、启动Activity的四种模式
我们前面说过,android通过Activity栈来管理启动的Activity,当启动一个Activity时就把它压入到栈顶,其实这只是android的一种默认实现,有时候如果栈中存在相同的Activity,通过设置不同的启动模式,就不一定需要新创建一个Activity了。
Activity有下面四种启动模式:
(1)standard
(2)singleTop
(3)singleTask
(4)singleInstance
通过在AndroidManifest.xml中设置android:lanuchMode,来设置不同的启动方式。
standard
默认的一种启动方式,每次通过Intent打开一个Activity,不管栈中是否已有相同的Activity,都会创建一个新的Activity,并放入到栈顶中。
singleTop
每次通过Intent打开一个启动模式为singleTop的Activity,系统会先判断栈顶中是否有该Activity,如果有,就不会创建新的Acitivity;如果栈顶没有,即使栈中的其他位置上有相同的Activity,系统也会创建一个
新的Activity。和standard模式很相似,只是会对栈顶元素进行判断,解决栈顶多个重复相同的Activity的问题。
当栈顶元素有相同的Activity时,再通过Intent打开同一个Activity不会创建新的对象,但是会调用OnNewIntent(Intent)方法,只有在栈顶有相同的Activity时才会调用这个方法,如果没有相同的,就类似于standard,不会调用OnNewIntent(Intent)方法了。
singleTask
如果栈中已经有该Activity的实例了,不管它在栈中什么位置,都会重用该Activity实例,如果它没在栈顶,此时,就会先把它上面的Activity实例先销毁掉,直到它成为栈顶元素。如果栈中不存在该实例,则会创建一个新的Activity实例放入栈中。当重用Activity时,也会调用OnNewIntent(Intent)方法.
singleInstance
系统会创建出一个新的栈,在这个新的栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。 比如,我们一个应用中打开了百度地图,然后在另一个应用中,也准备打开百度地图,此时,它会直接进入到刚才的地图画面,按返回时返回到自己的界面。
5、完全退出程序的几种方法
方案一:定义Activity栈
根据我们上面讲到的,每个Activity都会放入到栈中管理,那我们也可以仿照这个定义一个类似的Activity栈,每打开一个Activity,就把它放入到我们自定义的Activity中,因此我们写一个Activity管理类来管理这些Activity,代码如下:
- /**
- * APP管理类
- *
- */
- public class AppManager {
- private static Stack<Activity> activityStack;
- private static AppManager instance;
- private PendingIntent restartIntent;
- private AppManager() {
- }
- /**
- * 单一实例
- */
- public static AppManager getAppManager() {
- if (instance == null) {
- instance = new AppManager();
- }
- return instance;
- }
- /**
- * 添加Activity到堆栈
- */
- public void addActivity(Activity activity) {
- if (activityStack == null) {
- activityStack = new Stack<Activity>();
- }
- activityStack.add(activity);
- }
- /**
- * 获取当前Activity(堆栈中最后一个压入的)
- */
- public Activity currentActivity() {
- Activity activity = activityStack.lastElement();
- return activity;
- }
- /**
- * 结束当前Activity(堆栈中最后一个压入的)
- */
- public void finishActivity() {
- Activity activity = activityStack.lastElement();
- finishActivity(activity);
- }
- /**
- * 结束指定的Activity
- */
- public void finishActivity(Activity activity) {
- if (activity != null) {
- activityStack.remove(activity);
- activity.finish();
- activity = null;
- }
- }
- /**
- * 结束指定类名的Activity
- */
- public void finishActivity(Class<?> cls) {
- for (Activity activity : activityStack) {
- if (activity.getClass().equals(cls)) {
- finishActivity(activity);
- }
- }
- }
- /**
- * 结束所有Activity
- */
- public void finishAllActivity() {
- for (int i = 0, size = activityStack.size(); i < size; i++) {
- if (null != activityStack.get(i)) {
- activityStack.get(i).finish();
- }
- }
- activityStack.clear();
- }
- /**
- * 退出应用程序
- */
- public void exitApp(Context context) {
- try {
- finishAllActivity();
- System.exit(0);
- android.os.Process.killProcess(android.os.Process.myPid());
- } catch (Exception e) {
- }
- }
- }
方案二:利用广播的方式
这个可以具体看这篇文章:http://blog.csdn.net/way_ping_li/article/details/8031125
二、Fragment
Android是通过FragmentManager来管理Fragment,每次对Fragment进行添加和移除时需要开启事务,通过事务处理这些相应的操作,然后commit事务。
1、添加、移除Fragment的几种方式
在对Fragment进行管理前,需要开启一个事务,如下:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
FragmentTransaction下管理Fragment的主要方法有add()、remove()、replace()、hide()、show()、detach()、attach()。
添加Fragment方式一:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.add(R.id.content, new Fragment1(),"Fragment1");
tx.commit();
这里是直接通过add将Fragment1绑定到id为content的View上。
添加Fragment方式二:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.replace(R.id.content, new Fragment1(),"Fragment1");
tx.commit();
这里使用replace来添加Fragment,replace的作用相当于是remove() + add() 组合后的作用。即使用replace会先移除掉当前id为content上的Fragment,这个被移除掉的Fragment就会被销毁掉(如果当前事务),然后通过add再把新的Fragment添加到View上。
(1)使用replace方式,相当于在对应id为content的FrameLayout上只有一层,那就是上面的Fragment1,通过replace这种方式,会把Fragment的生命周期再走一遍,如果我们的Fragment中有获取数据的操作的话,会频繁的去拉取数据;使用replace,Fragment绑定的视图一定会销毁,Fragment实例不一定会销毁,主要看有没有添加到回退栈。
(2)而通过add方式,我们可以在id为content的FrameLayout上添加多层,也即可以通过多次add来添加多个Fragment到FrameLayout上。这个时候,我们就可以配合hide()、show()方法来不断切换不同的Fragment。在我们通过add方式添加了Fragment到FrameLayout 的View上之后,通过hide()、show()来切换Fragment还有一个优势就是,当一个Fragment重新show展示出来的时候,它原来的数据还保留在该Fragment上,也就是说hide并不会销毁Fragment,只是单纯的隐藏了而已。
推荐方式:
因此,推荐使用add、hide、show的方式管理Fragment。但是这种方式一些情况下也会有一个缺陷就是:可能会造成Fragment重叠。
比如底部有四个Tab:tab1,tab2,tab3,tab4,对应的Fragment引用为tab1Fragment,tab2Fragment,tab3Fragment,tab4Fragment,首先通过add将这四个Fragment添加到FragmentManager后,通过hide和show切换不同TAB都可以处于正常情况,当我们切换到tab1时,假设tab1上的Fragment为 tab1Fragment变量指向的(即tab1Fragment= new Fragment()),这个时候我们按下HOME键,如果长时间没有回到应用或者内存不足了,系统回收了该引用,此时tab1Fragment= null;但是,tab1的Fragment实例其实还是存在与内存中,只是引用被销毁了,这个时候,我们切换到tab2,这个步骤中,我们会把tab1的fragment隐藏掉,然后显示tab2,即如下操作:
tx.hide(tab1Fragment);
tx.show(tab2Fragment);
tx.commit();
但是,因为此时 tab1Fragment = null,引用变量为空,hide操作无法实现隐藏功能,但是又由于tab1上的Fragment实例还处于内存中,因此此时会造成tab2与tab1重叠的现象。再切换到tab1时,因为tab1Fragment = null,此时会再去创建一个新的Fragment,放入到tab1上,导致原来的tab1上的Fragment一直存在与内存中导致重叠,直至它被回收。
造成上述问题的原因还是因为我们无法找到那个实例对象Fragment,因为引用tab1Fragment已经为空了。这个时候,我们在add的时候可以给Fragment绑定一个tag,用它来标识该Fragment,如果引用为空了,再通过tag来找到该Fragment。如下:
- //在添加Fragment时
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction tx = fm.beginTransaction();
- tab1Fragment = new Fragment1();
- tx.add(R.id.content, tab1Fragment,"fragment1");
- tx.commit();
- //在使用时,比如切换到tab2时
- if(tab1Fragment != null){
- tx.hide(tab1Fragment);
- tx.show(tab2Fragment);
- tx.commit();
- }else{
- tab1Fragment = (Fragment1) fm.findFragmentByTag("fragment1");
- tx.hide(tab1Fragment);
- tx.show(tab2Fragment);
- tx.commit();
- }
关于上面的缺陷实例,具体可以看这篇文章:http://blog.csdn.net/shimiso/article/details/44677007
2、回退栈
当我们在一个Activity中有多个Fragment进行切换时,我们按下返回键时,会直接退出这个Activity,如果我们想在按下返回键时,退回到上一个显示的Fragment,而不是直接返回,我们就需要使用到回退栈了,类似于系统为每个应用创建的Activity栈一样,每个Activity也维护着一个事务回退栈,在我们通过事务对Fragment进行操作的时候,如果将这个事务添加到回退栈了,这个Fragment的实例就不会被销毁。按返回键时就可以回到这里。如下:
在MainActivity中,
- public class MainActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction tx = fm.beginTransaction();
- tx.add(R.id.content, new Fragment1(),"fragment1");
- tx.commit();
- }
- }
进入Activity时初始化加载第一个Fragment1,这里我们并没有把它加入到回退栈中,这是因为当目前显示的是Fragment1时,按下返回键我们就是需要直接退出,因为这是最初的那个Fragment,如果我们加入到了回退栈,那按下返回键后将是一片空白,再按一次返回键后才会退出这个Activity,这是因为第一次按下返回键时,相当于是将Fragment1从栈中弹出,此时被销毁了,Activity的FrameLayout也就没有了Fragment依附,因此一片空白。
在Fragment1中,有了一个按钮,点击后打开第二个Fragment2,
- public class Fragment1 extends Fragment implements OnClickListener {
- private Button mBtn;
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_one, container, false);
- mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
- mBtn.setOnClickListener(this);
- return view;
- }
- @Override
- public void onClick(View v) {
- Fragment2 fTwo = new Fragment2();
- FragmentManager fm = getFragmentManager();
- FragmentTransaction tx = fm.beginTransaction();
- tx.replace(R.id.content, fTwo, "fragment2");
- tx.addToBackStack(null); //将当前事务添加到回退栈
- tx.commit();
- }
- }
在Fragment2中,
- public class Fragment2 extends Fragment implements OnClickListener {
- private Button mBtn ;
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_two, container, false);
- mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
- mBtn.setOnClickListener(this);
- return view ;
- }
- @Override
- public void onClick(View v) { //打开Fragment3
- Fragment3 fThree = new Fragment3();
- FragmentManager fm = getFragmentManager();
- FragmentTransaction tx = fm.beginTransaction();
- tx.hide(this); //隐藏当前显示的Fragment2
- tx.add(R.id.content , fThree, "fragment3"); //添加Fragment3
- tx.addToBackStack(null); //将当前事务添加到回退栈
- tx.commit();
- }
- }
在Fragment3中我们只是打印了一些消息,就不再写了。
当当前显示到Fragment3时,我们按下返回键,将会显示出Fragment2出来,继续按下返回键,显示出Fragment1出来,再按下后,直接退出Activity。
因为我们在Fragment1和Fragment2中,在事务提交之前,即tx.commit()之前,我们把当前的事务(用新的Fragment替换当前显示Fragment或者hide当前Fragment)加入到了回退栈,即tx.addToBackStack(null),点击返回键后,就从回退栈中退出栈顶元素,即上一个加入的事务。
上面我们使用了前面介绍的两种添加Fragment的方式,即replace方式和hide()、add()方式,replace方式,Fragment绑定的视图一定会销毁,如果该事务加入到了回退栈,Fragment实例就不会被销毁,只是视图销毁了;而hide()、add()方式隐藏当前Fragment,加入新的Fragment,隐藏的Fragment绑定的视图也不会被销毁。
这里的视图销不销毁,指的是我们的数据有没有被保存下来,如果视图被销毁了,说明重新回到这个Fragment后,会重新进入onCreate及之后的周期方法区创建一个新的视图,这个时候数据肯定就不在了;
如果视图没有被销毁,在重新回到这个Fragment时,原来的输入数据还在,没有丢失。
当然,在Fragment里面也有onSaveInstanceState(Bundle)方法,可以通过这个来保存数据,然后再onCreate或其他方法里面获取到数据来解决数据丢失的问题。
3、与Activity通信
因为Fragment依附于Activity,Activity与Fragment通信,可以有以下几种办法:
(1)如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
(2)如果Activity中没有保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getSupportFragmentManager().findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。
(3)在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。
0 0
- Activity与Fragment易混点归纳
- Activity与Fragment易混点归纳
- Activity与Fragment易混点归纳
- fragment与fragment,activity与fragment传值
- Fragment与Activity通讯
- Fragment与Activity
- Fragment与Activity通信
- Activity与Fragment生命周期
- fragment与activity通讯
- Fragment与Activity通讯
- Fragment与Activity交互
- Fragment与Activity通讯
- Activity与Fragment
- Fragment与Activity生命周期
- Fragment与Activity生命周期
- Fragment与Activity通讯
- Fragment与Activity通信
- Fragment与Activity通讯
- 简单错误记录
- 1003
- 近期需要学习的知识点
- Database之增删改查工具类(简单版)
- 通过sql替换mysql 表某字段中的部分内容,
- Activity与Fragment易混点归纳
- Android Socket专题: UDP通信服务器端app的demo的实现
- Scrum简明入门
- 《深入理解SPARK:核心思想与源码分析》(前言及第1章)
- cannot simultaneously fetch multiple bags 的解决方法
- 最小生成树 prim算法
- 最长公共子序列
- 安卓菜鸟开发笔记(10-8)多媒体播放器02视频播放器
- eclipse常用以及实用的快捷键