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来持久化数据。如下例子:
@Overrideprotected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState);outState.putString("name","lly");}
当我们这个Activity被销毁而重新创建re-created的时候,通过onCreate(Bundle)中的参数获取到该信息,如下:@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); if(savedInstanceState != null){ name = savedInstanceState.getString("name");}}
注意:onSaveInstanceState()方法只是在Activity“很容易被销毁的时候调用”,它并不是Activity的生命周期方法,这个调用时机是不确定的,对于点击返回按钮这种主动行为不会去调用这个方法。【此处经网友提醒后修改--原来观点:网上很多说在按下HOME键和旋转屏幕的时候会去调用,但是经过我测试一下,发现不管是我按下HOME键还是旋转屏幕,这个方法都没有被调用。按我理解,这个方法只有在“很容易被销毁的时候调用”,这个尺度应该是系统根据手机具体内存资源情况决定是否调用。】
【修改后--在按下HOME键和旋转屏幕的时候会去调用,以及电话来电等突发状况下能够回调该方法。但是由于onSaveInstanceState不是Activity的生命周期方法,当我们的Activity处于onPause或后台时onStop,如果此时Activity被销毁,回到这个Activity的时候会进行re-created,Activity中原来的数据就会丢失。因此,为了更好的用户体验,最好在onPause中进行数据的保存工作。(至于具体是在onPause中保存,还是onSaveInstanceState中保存,我觉得还是看项目需求,大部分情况下,使用onSaveInstanceState能够满足需求)】
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文件中加入如下代码:
<!-- float_box为我们定义的窗口背景 ,这个不是必须的-->
<item name="android:windowBackground" > @drawable /float_box </code></pre></div>
<p>2.在res/drawable文件夹下新建float_box.xml文件,代码如下:</p>
<div>
<div><pre><code><?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">
=</code></pre></div>
</div>
<p>3.在AndroidMainifest.xml中Activity的声明中加入</p>
<div>
<p>android:theme= "@style/Theme.FloatActivity"</p>
</div>
<p>效果如图:</p>
<div><a target="_blank" href="http://img.blog.csdn.net/20160518083907360?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center">查看图片</a>
</div>
<div>
<p><span><span>图中显示了一个activity启动另一个activity的效果,布局文件是同一个。其中被启动的activity2是以对话框样式显示,不完全覆盖住启动它的activit1,类似alertDialog。</span></span></p>
<p><span><span>这与普通的activity不同,默认情况下,activity2会完全遮住activity1,启动activity2后,会调用activity1的onStop方法,而这种以对话框样式显示的activity不会,此时调用的是</span><span>onPause</span><span>()。(详见Activity的生命周期)</span></span></p>
</div>
</div>
<h2><span>4、启动Activity的四种模式</span></h2>
<p><span>我们前面说过,android通过Activity栈来管理启动的Activity,当启动一个Activity时就把它压入到栈顶,其实这只是android的一种默认实现,有时候如果栈中存在相同的Activity,通过设置不同的启动模式,就不一定需要新创建一个Activity了。</span></p>
<p><span>Activity有下面四种启动模式:</span></p>
<p>(2)<span>singleTop</span></p>
<p><span>(3)</span><span>singleTask</span></p>
<p><span>(4)</span><span>singleInstance</span></p>
<p>通过在AndroidManifest.xml中设置android:lanuchMode,来设置不同的启动方式。</p>
<p><span><span><span><strong>standard</strong></span></span>
</span></p>
<p><span><span>默认的一种启动方式,每次通过Intent打开一个Activity,不管栈中是否已有相同的Activity,</span><span><span>都会创建一个新的Activity</span></span><span>,并放入到栈顶中。</span></span></p>
<p><span><span><span><strong>singleTop</strong></span>
</span></span></p>
<p><span><span>每次通过Intent打开一个启动模式为singleTop的Activity,系统会<span>先判断栈顶中是否有该Activity,如果有,就不会创建新的Acitivity</span>;如果<span>栈顶没有,即使栈中的其他位置上有相同的Activity,系统也会创建一个</span></span></span></p>
<p><span>新的Activity</span>。和standard模式很相似,只是会对栈顶元素进行判断,解决栈顶多个重复相同的Activity的问题。
</p>
<p>当栈顶元素有相同的Activity时,再通过Intent打开同一个Activity不会创建新的对象,但是会调用<span>OnNewIntent</span>(Intent)方法,只有在栈顶有相同的Activity时才会调用这个方法,如果没有相同的,就类似于standard,不会调用OnNewIntent(Intent)方法了。</p>
<p><span><span><span><strong>singleTask</strong></span>
</span></span></p>
<p><span><span>如果栈中已经有该Activity的实例了,不管它在栈中什么位置,都会重用该Activity实例,如果它没在栈顶,此时,就会先把它上面的Activity实例先销毁掉,直到它成为栈顶元素。如果栈中不存在该实例,则会创建一个新的Activity实例放入栈中。当重用Activity时,也会调用<span>OnNewIntent</span>(Intent)方法.</span></span></p>
<p><span><span><span><strong>singleInstance</strong></span>
</span></span></p>
<p><span><span><span><span>系统会创建出一个新的栈</span>,在这个<span><span><strong>新的栈</strong></span></span>中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。
比如,我们一个应用中打开了百度地图,然后在另一个应用中,也准备打开百度地图,此时,它会直接进入到刚才的地图画面,按返回时返回到自己的界面。</span></span>
</span></p>
<h2><span><span><span><span><span>5、完全退出程序的几种方法</span></span></span></span></span></h2>
<p><span><span><strong>方案一:定义Activity栈</strong></span></span></p>
<p><span>根据我们上面讲到的,每个Activity都会放入到栈中管理,那我们也可以仿照这个定义一个类似的Activity栈,每打开一个Activity,就把它放入到我们自定义的Activity中,因此我们写一个Activity管理类来管理这些Activity,代码如下:</span></p>
<div><pre><code>/**
* 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) {
}
}
}</code></pre></div>
<p><strong>方案二:利用广播的方式</strong></p>
<div><span>这个可以具体看这篇文章:<a target="_blank" href="http://blog.csdn.net/way_ping_li/article/details/8031125"></a></span><a target="_blank" href="http://blog.csdn.net/way_ping_li/article/details/8031125">http://blog.csdn.net/way_ping_li/article/details/8031125</a></div>
<h2>二、Fragment</h2>
<div>
<p>Android是通过FragmentManager来管理Fragment,每次对Fragment进行添加和移除时需要开启事务,通过事务处理这些相应的操作,然后commit事务。</p>
<h2><span><span>1、添加、移除Fragment的几种方式</span></span></h2>
<p>在对Fragment进行管理前,需要开启一个事务,如下:</p>
<div>
<p> FragmentManager fm = getSupportFragmentManager();</p>
<p> FragmentTransaction tx = fm.beginTransaction();</p>
</div>
<p>FragmentTransaction下管理Fragment的主要方法有add()、remove()、replace()、hide()、show()、detach()、attach()。
</p>
<p><span><strong>添加Fragment方式一:</strong></span></p>
<div>
<p> FragmentManager fm = getSupportFragmentManager();</p>
<p> FragmentTransaction tx = fm.beginTransaction();</p>
<p> tx.<span>add</span>(R.id.content, new Fragment1(),"Fragment1");</p>
<p> tx.<span>commit</span>();</p>
</div>
<p>这里是直接通过add将Fragment1绑定到id为content的View上。</p>
<p><strong>添加Fragment方式二:</strong>
</p>
<p><strong> </strong><span>FragmentManager fm = getSupportFragmentManager();</span></p>
<p> FragmentTransaction tx = fm.beginTransaction();</p>
<p> tx.<span>replace</span>(R.id.content, new Fragment1(),"Fragment1");</p>
<p> tx.<span>commit</span>();</p>
<p>这里使用replace来添加Fragment,<span>replace的作用相当于是remove() + add() 组合后的作用</span>。即使用replace会先移除掉当前id为content上的Fragment,这个被移除掉的Fragment就会被销毁掉(如果当前事务),然后通过add再把新的Fragment添加到View上。</p>
<p>(1)使用replace方式,相当于在对应id为content的FrameLayout上只有一层,那就是上面的Fragment1,<span>通过replace这种方式,会把Fragment的生命周期再走一遍</span>,如果我们的Fragment中有获取数据的操作的话,会频繁的去拉取数据;<span>使用replace,Fragment绑定的视图一定会销毁,Fragment实例不一定会销毁</span>,主要看有没有添加到回退栈。</p>
<p>(2)而通过add方式,我们可以在id为content的FrameLayout上添加多层,也即可以通过多次add来添加多个Fragment到FrameLayout上。这个时候,我们就可以配合hide()、show()方法来不断切换不同的Fragment。在我们通过add方式添加了Fragment到FrameLayout 的View上之后,<span>通过hide()、show()来切换Fragment还有一个优势就是,当一个Fragment重新show展示出来的时候,它原来的数据还保留在该Fragment上,也就是说hide并不会销毁Fragment,只是单纯的隐藏了而已</span>。</p>
<p><span><strong>推荐方式:</strong></span></p>
<p>因此,<span>推荐使用add、hide、show的方式管理Fragment</span>。但是这种方式一些情况下也会有一个缺陷就是:<span><span><strong>可能会造成Fragment重叠</strong></span></span>。</p>
<p>比如底部有四个Tab:tab1,tab2,tab3,tab4,对应的Fragment<span>引用</span>为tab1Fragment,tab2Fragment,tab3Fragment,tab4Fragment,首先通过add将这四个Fragment添加到FragmentManager后,通过hide和show切换不同TAB都可以处于正常情况,当我们切换到tab1时,假设tab1上的Fragment为
tab1Fragment变量指向的(即tab1Fragment= new Fragment()),这个时候我们按下HOME键,如果<span>长时间没有回到应用</span>或者内存不足了,系统回收了该引用,此时tab1Fragment= null;但是,tab1的Fragment实例其实还是存在与内存中,只是引用被销毁了,这个时候,我们切换到tab2,这个步骤中,我们会把tab1的fragment隐藏掉,然后显示tab2,即如下操作:</p>
<div>
<p> tx.hide(tab1Fragment);</p>
<p> tx.show(tab2Fragment);</p>
<p> tx.commit();</p>
</div>
<p>但是,因为此时 tab1Fragment = null,引用变量为空,hide操作无法实现隐藏功能,但是又由于tab1上的Fragment实例还处于内存中,因此此时会造成tab2与tab1重叠的现象。再切换到tab1时,因为tab1Fragment = null,此时会再去创建一个新的Fragment,放入到tab1上,导致原来的tab1上的Fragment一直存在与内存中导致重叠,直至它被回收。</p>
<p>造成上述问题的原因还是因为我们无法找到那个实例对象Fragment,因为引用tab1Fragment已经为空了。这个时候,我们在add的时候可以给Fragment绑定一个tag,用它来标识该Fragment,如果引用为空了,再通过tag来找到该Fragment。如下: </p>
<div><pre><code>//在添加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();
}</code></pre></div>
<div>关于上面的缺陷实例,具体可以看这篇文章:<a target="_blank" href="http://blog.csdn.net/shimiso/article/details/44677007">http://blog.csdn.net/shimiso/article/details/44677007</a></div>
<h2><span><span>2、回退栈</span></span></h2>
<p>当我们在一个Activity中有多个Fragment进行切换时,我们按下返回键时,会直接退出这个Activity,如果我们想在按下返回键时,退回到上一个显示的Fragment,而不是直接返回,我们就需要使用到回退栈了,类似于系统为每个应用创建的Activity栈一样,<span>每个Activity也维护着一个<span>事务</span>回退栈</span>,在我们通过事务对Fragment进行操作的时候,如果将这个事务添加到回退栈了,这个Fragment的实例就不会被销毁。按返回键时就可以回到这里。如下:</p>
<p>在MainActivity中,</p>
<div><pre><code>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();
}
} </code></pre></div>
<p>进入Activity时初始化加载第一个Fragment1,这里我们并没有把它加入到回退栈中,这是因为当目前显示的是Fragment1时,按下返回键我们就是需要直接退出,因为这是最初的那个Fragment,如果我们加入到了回退栈,那按下返回键后将是一片空白,再按一次返回键后才会退出这个Activity,这是因为第一次按下返回键时,相当于是将Fragment1从栈中弹出,此时被销毁了,Activity的FrameLayout也就没有了Fragment依附,因此一片空白。</p>
<p>在Fragment1中,有了一个按钮,点击后打开第二个Fragment2,</p>
<div>
<div><pre><code>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();
}
} </code></pre></div>
</div>
<p>在Fragment2中,</p>
<div>
<div><pre><code>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();
}
} </code></pre></div>
</div>
<p>在Fragment3中我们只是打印了一些消息,就不再写了。</p>
<p>当当前显示到Fragment3时,我们按下返回键,将会显示出Fragment2出来,继续按下返回键,显示出Fragment1出来,再按下后,直接退出Activity。</p>
<p>因为我们在Fragment1和Fragment2中,在<span>事务提交之前,即tx.commit()之前</span>,我们把当前的事务(用新的Fragment替换当前显示Fragment或者hide当前Fragment)加入到了回退栈,即tx.addToBackStack(null),点击返回键后,就从回退栈中退出栈顶元素,即上一个加入的事务。</p>
<p>上面我们使用了前面介绍的两种添加Fragment的方式,即replace方式和hide()、add()方式,replace方式,<span>Fragment绑定的</span><span>视图一定会销毁</span>,如果该事务加入到了回退栈,Fragment实例就不会被销毁,只是视图销毁了;而hide()、add()方式隐藏当前Fragment,加入新的Fragment,隐藏的Fragment绑定的视图也不会被销毁。</p>
<p>这里的视图销不销毁,指的是我们的数据有没有被保存下来,<span>如果视图被销毁了,说明重新回到这个Fragment后,会重新进入onCreate及之后的周期方法区创建一个新的视图,这个时候数据肯定就不在了</span>;</p>
<p><span>如果视图没有被销毁,在重新回到这个Fragment时,原来的输入数据还在,没有丢失</span>。
</p>
<p>当然,<span>在Fragment里面也有onSaveInstanceState(Bundle)方法,可以通过这个来保存数据,然后再onCreate或其他方法里面获取到数据来解决数据丢失的问题</span>。</p>
<h2><span><span>3、与Activity通信</span></span></h2>
<p>
<span><span>因为Fragment依附于Activity,Activity与Fragment通信,可以有以下几种办法:</span></span></p>
<p>
(1)如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法</p>
<p>
(2)如果Activity中没有保存任何Fragment的引用,那么没关系,<span>每个Fragment都有一个唯一的TAG或者ID,可以通过getSupportFragmentManager().findFragmentByTag()或者findFragmentById()获得任何Fragment实例</span>,然后进行操作。</p>
<p>
(3)在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。</p>
</div>
</div>
<div>
<h4>我的同类文章</h4>
<div>
<p>
<label>
<span>Android技术初探<em>(41)</em></span>
</label>
</p>
</div>
</div>
</div>