首页 > 嗟来之食 > Activity初步,初学者必看 – 代码钢琴家 –
2016
08-10

Activity初步,初学者必看 – 代码钢琴家 –

Activity是什么?
Activity是一个可与用户交互并呈现组件的视图。通俗说就是运行的程序当前的这个显示界面。
如果你还不明白,那么你写过HTML吗,它就好比一个网页。我们程序中的用户视图,都是Activity类的一个实例。

Activity Manager与Activity回退栈
一个Android应用程序,可以有一个或者更多的Activity。Android系统通过一个Activity Manager 来管理所有应用程序的Activity,确切说
Activity Manager维护着一个Activity栈,也就是以栈的形式来管理Activity。这是什么意思呢?
每当程序创建并显示一个新的Activity,这个Activity就会被压到Activity栈的栈顶,原先的Activity就看不见了。置于栈顶的下一层。
从用户的角度看就是老Activity消失,这个Activity视图来到前台,显示。
当用户点击手机上的Back键,当前Activity就会被销毁,这个Activity从栈中弹出,下一层的Activity就会重新回到前台,显示。
这个机制就好比浏览器的回退按钮的作用:点击回退按钮,显示上一个浏览过的网页。
下面是一个抽象出来的Activity栈的模型图,并不是在向你解释手机液晶屏的制作工艺~

Activity的生命周期及常用方法

官方那张图已经被传烂了。为了不占用篇幅,让你看起来很累,我把它放到文章的最后。
在一个应用程序运行期间,它包含的Activity有多种生命周期状态。作为开发者,你不必像C++程序员管理他们的堆内存一样,手动管理这些Activity,它是由Activity Manager来管理的。
但是你可以在Activity的状态发生变化时,得到通知。比如,当Activity第一次创建时,onCreate()就会被调用,当Activity被销毁时,onDestroy()函数被调用。Activity还有一些其他的onXXX()形式的函数。
作为开发者,我们的主要精力就是Override这些函数,以便在Activiy发生相应的生命周期变化时,通过这些函数去处理(回应)一些我们关注的工作。

• onCreate(Bundle savedInstanceState)
这个函数在Activity第一次创建时调用。你可以在这个函数中做一些 "一次性工作",比如用户界面视图的初始化: setContentView(R.layout.xx) 再比如获取组件的引用 mXXX = (T)findViewById(R.id.xxx) 以及为组件绑定监听器 mXXX.setOnClickListener(new OnClickListener(){……});
savedInstanceState是一个Bundle对象,Bundle,顾名思义,代表数据的捆绑体(数据包)。它的作用后面讲。

•onStart()
当Activity显示并可见时,调用。 •onResume()
当Activity可以与用户交互(可被操作)时调用,在这个方法中播放动画或者music是个好主意 •onPause()
当Activity将被调回后台时调用,一般是因为某个其他的Activity出现在,挡住这个Activity而发生。
在这方法中,你最好保存你需要永久保存的数据,比如数据库数据,或者编辑的文本。 •onStop()
这个Activity完全不可见了,被压到后台。"我暂时不需要这个界面接口了","哪你就待在后台吧" 注意:如何内存使用紧缺,android会直接kill掉程序的进程,这个方法可能来不及被调用, •onRestart()
"刚刚被丢在后台的那个Activity,我现在又需要你了,你出来吧"
户从后台重新调回后台的Activity,然后onRestart()就会调用,接下来就调用onStart()方法。
onStop() ………….onRestart()………………..onStart() ……onResume() 丢到后台 调到前台从新开始 可见 可交互
•onDestroy()
当Activity被销毁前,调用。
注意:如何内存使用紧缺,android会直接kill掉程序的进程,这个方法可能来不及被调用. 从上面的概述来看,onStop()和onDestroy()方法会不会得到调用都是不能百分百保障的,因为一旦一个Activity被置于后台,在内存紧缺的 情况下,为了给新的Activity创建提供内存,android会直接干掉后台Activity的进程。 那么,最安全的阵营就是onPause()方法了。这是你必须知道的。

下面我们通过代码证实一下Activity的生命周期的转换机制。

MainActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity
{

public static String final TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Log.v(TAG,"onCreate");

}

@Override
protected void onStart()
{
super.onStart();
Log.v(TAG,"onStart");
}

@Override
protected void onResume()
{
// TODO Auto-generated method stub
super.onResume();
Log.v(TAG,"onResume");
}

@Override
protected void onPause()
{
// TODO Auto-generated method stub
super.onPause();
Log.v(TAG,"onPause");
}

@Override
protected void onRestart()
{
// TODO Auto-generated method stub
super.onRestart();
Log.v(TAG,"onRestart");
}

@Override
protected void onStop()
{
// TODO Auto-generated method stub
super.onStop();
Log.v(TAG,"onStop");
}

@Override
protected void onDestroy()
{
// TODO Auto-generated method stub
super.onDestroy();
Log.v(TAG,"onDestroy");
}

}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Activity life cycle test"
/>

</LinearLayout>

最后的一个屏幕旋转演示时卡住了,只能说Android虚拟机太吃内存了,我得加内存条了 – – 。大家可以自己测试。
依次如下操作,并在LogCat中查看程序打印的日志
启动应用程序 : onCreate … onStart … onResume
在onResume状态下,点击回退按钮
onPause … onStop … onDestroy
在onResume状态下,点击Home按钮
onPause … onStop 调出后台任务,重新回到这个Activity :
onRestart … onStart … onResume 在onResume状态下,旋转屏幕:
onPause … onStop … onDestroy … onCreate … onStart … onResume

Activity的数据保存与恢复

下面通过一个例子来说名Activity数据的保存和恢复的作用和过程。
例子的思路是这样的:在竖屏状态下,输入70,将初始为0 的 mTotal累加到70,并通过Toast显示,然后,切换手机为
横屏,再输入30,将mTotal累加到100,再次显示。
java文件

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity
{
private int mTotal = 0; //累计储存变量
private EditText mInputText = null;
private Button mSubmitButton = null;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mInputText = (EditText)findViewById(R.id.id_input_text);

mSubmitButton = (Button)findViewById(R.id.id_submit_button);
mSubmitButton.setOnClickListener(new View.OnClickListener()
{

@Override
public void onClick(View v)
{
try{
int inputNum = Integer.parseInt(mInputText.getText().toString());
mTotal+=inputNum;
showToast(String.format("total=%d", mTotal));
}catch(Exception e)
{
showToast("请输入一个数!!!");
}

}
});

}

private void showToast(String s)
{
Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
}

}

布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >

<EditText
android:id="@+id/id_input_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberSigned"
android:background="#55667A"
android:textColor="#000000"
android:textSize="20sp"

/>
<!–android:inputType="numberSigned" 表示只允许输入带符号的数–>
<Button android:id="@+id/id_submit_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="提交" /> </LinearLayout>

结果出乎意外,Toast显示为30,并不是100,。那么先前输入的70跑哪里去呢?
通过前面对Activity生命周期的状态切换的例子发现:当手机旋转后,原来的Activity被销毁了,取而代之的是一个新建的Activity实例。
而total变量是Activity实例的一个字段,也就是说,total为70时的Activity随着Activity销毁而销毁了。新的Activity创建时,新的mTotal被初始化为0。
所以mTotal的值并没有随着屏幕旋转得到保留。

怎么解决这个问题呢?下面再介绍2个Activity的onXXX()形式的函数。

&bull;onSaveInstanceState(Bundle outState)
SaveInstanceState,很明显,意为保存实例状态(数据),on,表明它是一个触发函数。
它的参数是一个Bundle类对象,也就是数据包对象,我们可以 将需要保存的数据,put到Bundle对象outState中。从数据结构的角度上说,Bundle其实就是
一个Map,有的也叫Dictionary,是通过key – value的形式存取数据的。
同样,onSaveInstanceState方法也是自动被调用的,也就是说,你不用关心onSaveInstanceState何时被调用, 你只需关心,你需要保存哪些数据 。
确切说,onSaveInstanceState是在onPause() 调用之后被调用。 这也符合常理,当Activity有被销毁的趋势时,就开始保存需要保存的数据,为下次创建使用。
况且onPause()是最安全的阵营。onStop()和onDestroy()方法能不能被调用是不能百分百保证的。
如果不复写这个方法,默认情况下,这个方法会保存所有的控件状态数据。
请注意上面图片中,EditText输入框中的内容70,它并没有在屏幕旋转后清空,说明默认的onSaveInstanceState()方法起到作用了。
不信你可以测试一下,复写onSaveInstanceState方法,注释掉super.onSaveInstanceState(outState),输入数字旋转屏幕,会发现数字消失了。
那么,复写这个方法的意义在于,在MVC的Model层,保存你自定义的数据。这正符合我们的意图:保存mTotal &bull;onRestoreInstanceState(Bundle savedInstanceState) 上面介绍了如何暂存数据,这个当然就是如何将数据再取出来啦!
当一个Activity重新创建并初始化时,这个方法就会调用,在这里重新获取先前通过onSaveInstanceState(Bundle outState)方法保存的Activity销毁
前保留的数据。 默认情况下,这个方法会重新初始化用户控件的状态。 onRestoreInstanceState()会在onResume()之前调用,因为我们需要在activity可以与用户交互前做数据的恢复工作,这同样也符合常理。

下面开始fix前面mTotal例子的BUG!
修改后的java文件

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity
{

//为存储的数据对象创建一个KEY
public static final String KEY_MTOTAL = "MainActivity.MTOTAL";

private int mTotal = 0;
private EditText mInputText = null;
private Button mSubmitButton = null;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mInputText = (EditText)findViewById(R.id.id_input_text);

mSubmitButton = (Button)findViewById(R.id.id_submit_button);
mSubmitButton.setOnClickListener(new View.OnClickListener()
{

@Override
public void onClick(View v)
{
try{
int inputNum = Integer.parseInt(mInputText.getText().toString());
mTotal+=inputNum;
showToast(String.format("total=%d", mTotal));
}catch(Exception e)
{
showToast("请输入一个数!!!");
}

}
});

}

@Override
protected void onSaveInstanceState(Bundle outState)
{

super.onSaveInstanceState(outState); //存储空间状态
outState.putInt(KEY_MTOTAL, mTotal); //存储自定义数据,key – value形式

}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState); //恢复控件状态

if(savedInstanceState!=null)
{
//通过KEY,提取mTotal的值,最后一个参数0表示如果提取失败,则让mTotal为0
mTotal = savedInstanceState.getInt(KEY_MTOTAL , 0);

}

}

private void showToast(String s)
{
Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
}

}

Bundle形象点说就时一个暂存数据的"包裹",它为横屏Activity和竖屏Activity之间数据传递搭建了一个桥梁。
Bundle对象中有很多putXXX和getXXX形式的方法,用来存取数据,这些数据都是通过key – value的形式存取的,所以你需要为存取的数据定义一个
独一无二的KEY值(String类型)。同时你还需要注意的是,Bundle只能存取一些常用的内置类型数据,如果你要存储你自定义的类型,则需要让你的类
实现Parcelable或者Serializable接口,限于篇幅,这里不再扩展,以后我会写出来分享。
细心的同学会发现,onCreate方法的参数也是一个Bundle,你也可以在onCreate方法中来恢复数据。不过我还是建议大家让每个函数各尽其职。

Activity与进程的关系
进程 != 程序
实质上讲,每一个用户操作视图,就是一个Activity的实例,每一个Activity的实例都有自己的生命周期。
应用程序就是一个或多个Activity,再加上容纳这些Activity并为Activity提供服务的进程。
在android中,即便一个应用程序的进程被干掉了,它仍然可以是“活着的”,因为进程并不是和Activity绑定的。进程就像公交车,而Activity就是乘客,乘客的一天也是有不同的状态的,天亮了,起床乘公交上班,中午,乘公交回家吃饭,下午,乘公交回家。可以发现,同样的道理,乘客并不依赖某一辆公交,比如上班的时候坐的是25路,回家也可以坐32路公交,只要这辆车能为乘客完成自己的状态转换就行。没有公交车,乘客永远不能完成自己的状态转换,没有进程,Activity也不能完成自己生命周期的切换,这也是为什么onStop()和onDestroy()方法不能得到调用的原因。

最后编辑:
作者:
这个作者貌似有点懒,什么都没有留下。

留下一个回复