基本用法
有点意思
Android 5.0系统中, Android Studio开发环境下. 新建一个活动继承自Activity, 运行是没有标题栏的. 但默认继承的是ActionBarActivity
, 运行的话是有标题栏的. ActionBarActivity
是过时的API….
在活动中使用Toast:
Toast.makeText(FirstActivity.this, "You clicked Button 1", Toast.LENGTH_SHORT).show();
静态方法makeText(), 三个参数.
用完不要忘记调用show()方法.
在活动中使用Memu
- 新建menu文件.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add" />
<item
android:id="@+id/remove_item"
android:title="Remove" />
</menu>
- 在活动中重写onCreateOptionsMenu()方法.
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
- 定义菜单响应事件. 重写onOptionsItemSelected()方法.
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
注意:在5.0以下版本中, 继承自Activity, 这样写的话按menu键没有问题.
但是在5.0以上版本中, 该活动只有继承自ActionBarActivity
, 在上面的ActionBar中才会有menu菜单. 按底部三个键右边那个会让你选择哪个应用.
这就是5.0以上系统的不同之处吧.
- 销毁一个活动:
finish()
方法.
效果等同于按下Back键.
使用Intent
使用显式Intent
startActivity(new Intent(FirstActivity.this, SecondActivity.class));
这种”意图”非常明显的方式, 就是显式.
使用隐式Intent
不明确指定要启动哪个活动, 而是指定抽象的action category等信息, 让系统去分析该启动哪个活动.
am文件中给想要启动的活动添加intent-filter:
<intent-filter>
<action android:name="me.zipstream.activity.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
可以指定action和category等信息.
注意: category
一项, 系统提示时要注意别选成全是大写的那个.前面三个词都是小写.不然会报错. android.intent.category.DEFAULT
表示默认的category, 调用startActivity()
方法时会自动添加到Intent中.如果不是DEFAULT, 就需要手工指定了.
Intent intent = new Intent("me.zipstream.activity.ACTION_START");
startActivity(intent);
这就是隐式Intent的使用方式.
要求和中内容同时匹配Intent中指定的相应信息.才能正确响应.
如果am文件中的category除了默认的还有别的, 怎么在Intent中添加呢?
比如<category android:name="me.zipstream.activity.MY_CATEGORY" />
这时在隐式使用Intent时,
Intent intent = new Intent("me.zipstream.activity.ACTION_START");
intent.addCategory("me.zipstream.activity.MY_CATEGORY");
startActivity(intent);
隐式Intent的更多用法
隐式Intent还可以用于启动其他程序的活动. 即实现多个应用程序之间的功能共享.
比如调用系统浏览器打开某个网页:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
其中Intent.ACTION_VIEW
是系统内置常量, 其值为android.intent.action.VIEW
setData()方法其实和addCategory()差不多.也就是说, 在标签中可以配置一个标签, 用于更精确的指定当前活动能够响应什么类型的数据.标签中可以配置以下内容:
1.android:scheme
指定数据协议部分, 如http
2. android:host
指定数据的主机名部分, 如www.baidu.com
3. android:port
指定数据的端口部分
4. android:path
指定主机名和端口之后的部分, 如一段网址中跟在域名之后的内容.
5. android:mimeType
指定可以处理的数据类型, 允许使用通配符的方式进行指定.
同样, 只有标签中指定的内容和Intent携带的Data完全一致时, 当前活动才可以响应该Intent.
比如第三方浏览器的实现原理, 就是在am文件中这么注册活动的:
<activity android:name=".ThirdActivity" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
</activity>
再比如调用系统拨号盘:
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
向下一个活动传递数据
Intent中提供了一系列putExtra()
方法的重载, 把要传递的数据暂存在Intent中, 只需要在下一个活动中从Intent中取出就可以了. 数据是以键值对的形式存放的. 取出数据也很简单, 首先调用getIntent()
方法得到Intent对象, 再调用Intent的get[type]Extra()
方法即可取出.
比如, 一个活动向下一个活动传递数据:
String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("extra_data", data);
startActivity(intent);
下一个活动取出数据:
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("SecondActivity", data);
返回数据给上一个活动
安卓中返回上一个活动只需按一下Back键就可以了, 没有一个用于启动活动的Intent来传递数据. 在API中, Activity提供了一个startActivityForResult()
方法, 用来实现这个需求. 该方法接受两个参数, 一个是Intent, 另一个是int类型的请求码, 只要是一个唯一值就可以了.
在被启动的活动中, 通过setResult()
方法向上返回数据, 该方法接受两个参数, 第一个参数用于向上一个活动返回处理结果, 一般只使用RESULT_OK
或RESULT_CANCELED
这两个值. 第二个参数是Intent对象, 用于把带有的数据传回去.
数据返回是返回了, 但是上一个活动怎么接受这个数据呢? 由于在一个返回数据的活动被销毁之后会回调上一个活动的onActivityResult()
方法, 因此重写当前活动的onActivityResult()
方法. 这个方法接受3个参数(int requestCode, int resultCode, Intent data), 第一个参数是启动活动时传入的请求码, 第二个参数是返回数据时传入的处理结果, 第三个参数是携带者返回数据的Intent. 方法体中通过swtich(requestCode)来确定哪一个活动返回来的数据(requestCode唯一).
startActivityForResult: 字面理解, “为了得到数据而启动活动”…
onActivityResult: 字面理解, “在活动得到返回数据时调用, 在方法体里处理逻辑…”
启动下一个活动, 要求返回数据:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
在下一个活动中返回数据:
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
接受返回的数据:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnData);
}
break;
default:
break;
}
}
如果用户在返回上一个活动时, 是以按下Back键来返回的, 就需要重写onBackPressed()
方法来解决:
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
活动的生命周期
返回栈
Android使用任务(Task)来管理活动. 一个任务就是一组存放在栈里的活动的集合, 这个栈也叫返回栈.
系统总是会显示栈顶的活动给用户.
活动的生存期
七个回调方法, 覆盖了活动生命周期的每一个环节.
onCreate()
- 活动第一次创建时调用. 在此方法里完成初始化操作, 例如加载布局\绑定事件等.
onStart()
- 活动由不可见变为可见的时候调用.
onResume()
- 活动准备好和用户进行交互时候调用. 处于栈顶状态.
onPause()
- 系统准备启动或者恢复另一个活动时候调用. 通常在此方法中将一些消耗CPU的资源释放, 以及保存一些关键数据. 这个方法的执行速度必须要快.
onStop()
- 活动完全不可见的时候调用. 与onPause()的区别: 如果启动的新活动是一个对话框式的, 则onPause()方法执行, onStop()方法不执行.
onDestroy()
- 活动被销毁之前调用.
onRestart()
- 活动由停止状态变为运行状态之前调用. 即活动被重新启动了.
根据七个方法, 活动可分为三个生存期:
- 完整生存期: onCreate()和onDestroy()之间.
- 可见生存期: onStart()和onStop()之间.
- 前台生存期: onResume()和onPause()之间.
一张图说明活动的生命周期:
体验活动的生命周期
创建一个项目, 主活动的布局时两个按钮, 用于切换到子活动. 创建两个子活动, 分别用来显示NormalActivity和DialogActivity. 分别加载normal_layout和dialog_layout这两个布局文件. 这两个布局文件里都只含有一个TextView, 一行说明文字. 主活动中覆盖七个回调方法, 方法体内打印一行log.
那么, 既然两个布局文件几乎一样, 怎么设定一个是普通活动另一个是对话框活动呢?
在am文件中注册活动时, 给对话框的活动指定一个主题就好了:
<activity android:name=".NormalActivity" />
<activity
android:name=".DialogActivity"
android:theme="@android:style/Theme.Dialog" />
编好代码运行程序, 观察活动的七个回调方法的运行时机, 加深理解.
活动被回收了怎么办
活动A的基础上启动了活动B, 这时A进入了停止状态, 若此时系统内存不足, A被回收掉了. 用户按下Back返回A时, 并不会执行onRestart()
, 而是执行A的onCreate()
方法. 这时产生一个严重的问题, 如果A中存在临时的数据或状态, 重新创建A活动这些数据都会消失, 用户体验极差.
Activity中提供了一个onSaveInstanceState()
回调方法, 保证一定在活动被回收之前调用. 因此可以用这个方法来解决上述问题.
这个方法会携带一个Bundle类型的参数, Bundle提供了一系列方法put[Type]
用于保存各种基本数据类型的数据. 数据同样以键值对的形式存储.
于是重写onSaveInstanceState()
方法, 用来保存数据:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Sth u just typed";
outState.putString("data_key", tempData);
}
那么, 保存下来的数据在哪里取出呢? 其实onCreate()
方法就携带了一个Bundle对象, 在此方法中判断Bundle对象是否为空, 不为空的话可以用getString()
方法取出数据:
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
//可以加入各种处理逻辑...
}
活动的启动模式
可以在am文件中通过给<activity>
标签指定android:launchMode
属性来选择启动模式.
活动的启动模式一共有四种: standard\singleTop\singleTask\singleInstance.
实际项目中应该根据特定的需求为每个活动指定恰当的启动模式.
standard
不显式指定情况下, 活动默认都是standard启动模式.这种模式下, 每当启动一个活动, 系统不会在乎此活动在返回栈中是否已经存在, 都会创建该活动的一个新的实例, 在返回栈中入栈, 并处于栈顶位置.
比如在FirstActivity这个活动为按钮设置点击事件, 来启动这个活动:
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
启动后点击两次这个按钮, 观察Logcat, 会发现这个活动又创建了两次新的实例, 此时返回栈中应该存在三个FirstActivity的实例. 需要连按三次Back键才能退出程序.
singleTop
该模式下, 启动活动时如果发现返回栈的栈顶已经是该活动, 则直接使用它, 不会再创建新的实例.
在am文件中注册该活动的标签内添加:
android:launchMode="singleTop"
再启动FirstActivity, 不管点击多少次按钮, 都只有一个实例, 只按一下Back键就能退出程序.
要注意, 如果要启动的活动目前不在栈顶位置, 则启动时, 还是会创建这个活动的实例.
singleTask
singleTop模式能够解决重复创建栈顶活动的问题, 但是如果活动没在栈顶, 还会创建多个实例的.
singleTask模式可以使活动在整个应用程序的上下文中只存在一个实例. 具体过程是:
首先在返回栈中检查是否存在该活动的实例, 如果存在则直接使用该实例, 并把这个活动之上的所有活动统统出栈, 如果没有发现就会创建一个新的实例.
singleInstance
该模式会有一个单独的返回栈来管理这个活动, 不管是哪个应用程序来访问这个活动, 都共用同一个返回栈, 可以解决共享活动实例的问题.
比如有三个活动, A, B, C, B的启动模式为singleInstance, 活动A点击按钮跳转到活动B, 活动B点击按钮跳转到活动C, 在每个活动的onCreate()方法中打印这个活动的返回栈id, (通过getTaskID()方法), 观察Logcat, 发现A和C处于同一个栈, B是单独的返回栈. 如果当前显示的是C活动, 则按下Back键返回的是A活动, 再按下Back键返回到B活动, 再按Back键退出程序.
活动的最佳实践
知道当前处于哪一个活动
首先新建一个 BaseActivity 类, 来继承Activity, 重写它的 onCreate() 方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
然后让当前程序的所有活动都继承这个 BaseActivity 类, 这样我们在进入某个页面时, Logcat就会输出当前活动的名字了.
随时随地退出程序
用一个专门的集合类对所有活动进行管理.
新建一个 ActivityCollector
类:
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<Activity>();
public static void addActivity(Activity activity) {
activities.add(activity);
}
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
这个类提供三个方法, 用来添加活动, 移除活动, 结束所有活动.
然后, 在 BaseActivity 的 onCreate() 方法中调用 addActicity() 方法, 在 onDestroy()
方法中调用 removeActivity() 方法.
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
此后, 只要我们想在某一个活动中退出程序时, 只需要直接调用 ActivityCollector.finishAll() 就可以了.
启动活动的最佳写法
活动A要启动活动B, 最好在活动B中把启动活动的代码单独封装起来, 并把传递数据等语句都写在这个方法中, 可以使启动活动更简单, 代码更清晰易懂.
在活动B的类中添加方法:
public static void actionStart(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
这样, 想要在活动A中启动它, 只需要一行代码就搞定了.
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");