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()方法,进行回到前台的准备工作。
The visible lifetime(可见的生命周期) :从onStart()到进入onStop()前(即onStart-->onPause),这个Activity都可被用户看见,这期间,不一定处于前台,也不一定能够供用户交互(比如处于onPause状态时)
The foreground lifetime(前台生命周期):从onResume()到进入onPause前,这个期间,Activity处于可以和用户交互时期。这个时期,可能也会频繁在onResume和onPause状态间切换。
2、保存状态信息(推荐在onPause中保存)
官方文档中是推荐我们在onPause()中区保存信息的,比如将数据写入到SharedPreference中。Android还提供了onSaveInstanceState(Bundle)方法来保存数据,通过Bundle来持久化数据。如下例子:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("name","lly");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState != null){
name = savedInstanceState.getString("name");
}
}
注意:onSaveInstanceState()方法只是在Activity“ 很容易被销毁的时候调用”,
它并不是Activity的生命周期方法,这个调用时机是不确定的,对于点击返回按钮这种主动行为不会去调用这个方法。网上很多说在按下HOME键和旋转屏幕的时候会去调用,但是经过我测试一下,发现不管是我按下HOME键还是旋转屏幕,这个方法都没有被调用。按我理解,这个方法只有在“很容易被销毁的时候调用”,这个尺度应该是系统根据手机具体内存资源情况决定是否调用。因此官方文档中
推荐在onPause中进行数据信息的保存操作。(
Note that it is important to save persistent data in
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.)
特别地,在View中,使用on onSaveInstanceState时,对应的这个View一定要在布局文件中定义id,否则没有id,是不会进入到ononSaveInstanceState方法中的。
3、如何将一个Activity设置成窗口的样式
1.在res/values文件夹下的style.xml文件中加入如下代码:
[html] view plain copy
- <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文件,代码如下:
[html] view plain copy
- <?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
singleTop
(3) singleTask
(4) singleInstance
通过在AndroidManifest.xml中设置android:lanuchMode,来设置不同的启动方式。
standard
默认的一种启动方式,每次通过Intent打开一个Activity,不管栈中是否已有相同的Activity,都会创建一个新的Activity,并放入到栈顶中。
singleTop
每次通过Intent打开一个启动模式为singleTop的Activity,系统会先判断栈顶中是否有该Activity,如果有,就不会创建新的Acitivity;如果栈顶没有,即使栈中的其他位置上有相同的Activity,系统也会创建一个
新的Activity。和standard模式很相似,只是会对栈顶元素进行判断,解决栈顶多个重复相同的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,代码如下:
[java] view plain copy
- /**
- * 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) {
- new AppManager();
- }
- return instance;
- }
- /**
- * 添加Activity到堆栈
- */
- public void addActivity(Activity activity) {
- if (activityStack == null) {
- 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();
- 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();
- 0);
- android.os.Process.killProcess(android.os.Process.myPid());
- catch (Exception e) {
- }
- }
- }
方案二:利用广播的方式
这个可以具体看这篇文章:
二、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();
add(R.id.content, new Fragment1(),"Fragment1");
commit();
这里是直接通过add将Fragment1绑定到id为content的View上。
添加Fragment方式二:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
replace(R.id.content, new Fragment1(),"Fragment1");
commit();
replace的作用相当于是remove() + add() 组合后的作用。即使用replace会先移除掉当前id为content上的Fragment,这个被移除掉的Fragment就会被销毁掉(如果当前事务),然后通过add再把新的Fragment添加到View上。
通过replace这种方式,会把Fragment的生命周期再走一遍,如果我们的Fragment中有获取数据的操作的话,会频繁的去拉取数据; 使用replace,Fragment绑定的视图一定会销毁,Fragment实例不一定会销毁,主要看有没有添加到回退栈。
通过hide()、show()来切换Fragment还有一个优势就是,当一个Fragment重新show展示出来的时候,它原来的数据还保留在该Fragment上,也就是说hide并不会销毁Fragment,只是单纯的隐藏了而已。
推荐方式:
推荐使用add、hide、show的方式管理Fragment。但是这种方式一些情况下也会有一个缺陷就是: 可能会造成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。如下:
[java] view plain copy
- //在添加Fragment时
- FragmentManager fm = getSupportFragmentManager();
- FragmentTransaction tx = fm.beginTransaction();
- new Fragment1();
- "fragment1");
- tx.commit();
- //在使用时,比如切换到tab2时
- if(tab1Fragment != null){
- tx.hide(tab1Fragment);
- tx.show(tab2Fragment);
- tx.commit();
- else{
- "fragment1");
- tx.hide(tab1Fragment);
- tx.show(tab2Fragment);
- tx.commit();
- }
关于上面的缺陷实例,具体可以看这篇文章:
2、回退栈
每个Activity也维护着一个事务回退栈,在我们通过事务对Fragment进行操作的时候,如果将这个事务添加到回退栈了,这个Fragment的实例就不会被销毁。按返回键时就可以回到这里。如下:
在MainActivity中,
[java] view plain copy
- 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();
- new Fragment1(),"fragment1");
- tx.commit();
- }
- }
进入Activity时初始化加载第一个Fragment1,这里我们并没有把它加入到回退栈中,这是因为当目前显示的是Fragment1时,按下返回键我们就是需要直接退出,因为这是最初的那个Fragment,如果我们加入到了回退栈,那按下返回键后将是一片空白,再按一次返回键后才会退出这个Activity,这是因为第一次按下返回键时,相当于是将Fragment1从栈中弹出,此时被销毁了,Activity的FrameLayout也就没有了Fragment依附,因此一片空白。
在Fragment1中,有了一个按钮,点击后打开第二个Fragment2,
[java] view plain copy
- public class Fragment1 extends Fragment implements OnClickListener {
- private Button mBtn;
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- false);
- mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
- this);
- return view;
- }
- @Override
- public void onClick(View v) {
- new Fragment2();
- FragmentManager fm = getFragmentManager();
- FragmentTransaction tx = fm.beginTransaction();
- "fragment2");
- null); //将当前事务添加到回退栈
- tx.commit();
- }
- }
在Fragment2中,
[java] view plain copy
- public class Fragment2 extends Fragment implements OnClickListener {
- private Button mBtn ;
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- false);
- mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
- this);
- return view ;
- }
- @Override
- public void onClick(View v) { //打开Fragment3
- new Fragment3();
- FragmentManager fm = getFragmentManager();
- FragmentTransaction tx = fm.beginTransaction();
- this); //隐藏当前显示的Fragment2
- "fragment3"); //添加Fragment3
- null); //将当前事务添加到回退栈
- tx.commit();
- }
- }
在Fragment3中我们只是打印了一些消息,就不再写了。
当当前显示到Fragment3时,我们按下返回键,将会显示出Fragment2出来,继续按下返回键,显示出Fragment1出来,再按下后,直接退出Activity。
事务提交之前,即tx.commit()之前,我们把当前的事务(用新的Fragment替换当前显示Fragment或者hide当前Fragment)加入到了回退栈,即tx.addToBackStack(null),点击返回键后,就从回退栈中退出栈顶元素,即上一个加入的事务。
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方法
每个Fragment都有一个唯一的TAG或者ID,可以通过getSupportFragmentManager().findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。
(3)在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。