0%

第2章:先从看得到的入手

Intent使用

显式地就不说了,使用隐式的Intent时并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。

普通的隐式Intent使用

比如在AndroidManifest.xml中声明activity的时候,可以添加:

1
2
3
4
5
<intent-filter>
<action android:name="com.example.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<intent-filter>

在代码中就能以下面代码来启动这个activity了(由于category是DEFAULT,所以在intent中并未指定category了):

1
2
Intent intent = new Intent("com.example.ACTION_START");
startActivity(intent);

如果在AndroidManifest.xml中声明activity的时候同时指定了actioncategory,那么必须要在Intent中严格匹配才能打开,否则可能报错,比如写成:

1
2
3
4
5
6
<intent-filter>
<action android:name="com.example.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.example.MY_CATEGORY"/>
<intent-filter>

则代码中必须添加以下代码才能正确运行。

1
2
3
Intent intent = new Intent("com.example.ACTION_START");
**intent.addCategory("com.example.MY_CATEGORY");**
startActivity(intent);

更多隐式Intent用法

使用隐式的Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,比如说要在应用程序中点击一个按钮,然后要在浏览器中打开一个网页,则使用以下代码:

1
2
3
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(Intent);

这里面,setData()接收一个Uri对象,主要用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。那如果我们要自己写个浏览器应用,让其它应用也能像这样利用我们的APP打开网页,又该怎么做呢,这就要求在中添加一个<data标签>,用于更精确地指定当前活动能够响应什么类型的数据。标签中可以配置以下内容:

  • android:scheme。用于指定数据的协议部分,例如上例中的http部分。
  • android:host。用于指定数据的主机名部分,如上例中的www.baidu.com。
  • android:port。用于指定数据的端口部分。
  • android:path。用于指定主机名和端口之后的部分。

所以,如果我们要做一个浏览器,至少要在AndroidManifest.xml对主activity声明:

1
2
3
4
5
6
7
<intent-filter>
<action android:name="com.example.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http">

<intent-filter>

下次其他APP需要用http协议打开网页时,我们的APP也会在候选列表中了。除了http协议意外,我们还可以指定很多其他协议,比如geo表示地理位置、tel表示拨打电话。

活动的生命周期

  • onCreate(),活动第一次被创建的时候调用,应该在这里面完成活动的初始化操作。
  • onStart(),在活动由不可见变为课件的时候调用。
  • onResume(),在活动准备好和用户进行交互的时候调用,此时活动一定位于返回栈的栈顶,并且处于运行状态。
  • onPause(),在系统准备去启动或者恢复另一个活动的时候调用,通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但是工作不能太多,不然会影响下一个Activity的使用。
  • onStop(),活动完全不可见的时候调用,它和onPause主要的区别在于,如果启动的新活动是一个对话框式的活动,那么onPause方法会得到执行,而onStop不会执行。
  • onDestroy(),活动晓辉之前调用。
  • onRestart(),由停止状态变为运行状态之前调用。一般是由上一个活动返回到当前活动。

Activity声明周期

活动回收了怎么办

想象以下场景,应用中有活动A,在A的基础上启动活动B,活动A此时进入了停止状态,此时由于内存不足,将活动A回收了,然后用户按Back键返回活动A,会出现什么情况呢?其实还是会正常显示A,只不过这是并不会执行onRestart方法,而是会执行活动A的onCreate方法,因为活动A在这种情况下会被重新创建一次。

如果A进程中有输入框,并且已经输入了一些文字了,如果回收被重新创建,那么会丢失输入的信息,影响用户体验。Activity中还提供了一个onSaveInstanceState()回调方法,这个方法可以保证在活动回收之前一定会被调用,这个方法会携带一个Bundle类型的参数,它允许以key-value的形式存取值,我们可以这样将要保存的数据存下来:

1
2
3
4
5
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("name","glassx");
}

数据已经保存下来了,但是在哪里恢复呢?其实我们一直使用的onCreate方法其实也有一个Bundle类型的参数,这个参数一般情况下是null,如果在活动呗系统回收之前有通过onSaveInstanceState保存的话,这个参数就会带有之前所保存的全部数据,因此通过以下方法取即可:

1
2
3
4
5
6
7
8
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if(savedInstanceState != null){
String name = savedInstanceState.getString("glassx");
}
}

活动的启动模式

在实际项目中我们应该根据特定的需求为每个活动指定恰当的启动模式,启动模式一种四种:standard、singleTop、singleTask、singleInstance

  • standard是默认的启动模式。每次启动都会创建一个新的实例。

  • singleTop:有些情况下,可能会觉得standard不太合理,活动明明已经在栈顶了,为毛还要再创建新的实例呢?singleTop模式可以解决这个问题,当活动以该模式启动时,如果发现返回栈的栈顶已经是该活动,那就直接使用它,不创建新的实例,并且调用栈顶实例的onNewIntent方法;如果栈顶不是该活动,就创建该活动的新的实例。

  • singleTask:如果活动的启动模式指定为singleTask,那么每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果有,把这个活动之上的所有活动统统出栈,并且直接使用该实例,并调用该实例的onNewIntent方法??(存疑,等会实践下)。反之没有的话就创建该活动的实例。

  • singleInstance:指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。

    那么这样做有什么意义呢?想象以下场景,我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?前面3中模式做不到,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管哪个应用来访问这个活动,都公用一个返回栈,也就解决了共享活动实例的问题。

注意:如果三个活动,A和C都是standard模式,B是singleInstance模式,那么A启动B,B启动C后,在C界面按返回键是回退到A,再按返回键回退到B,接着按返回键才会退出应用,因为A和C是同一个回退栈中,B单独在一个栈中,可以用如下图来理解这一过程

Activity声明周期

活动的最佳实践

知晓当前是在哪一个活动

建一个BaseActivity,在onCreate的时候打印出来当前实例的类名,之后其他的activity都继承这个activity即可:

1
2
3
4
5
6
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Log.d("BaseActivity",getClass().getSimpleName());
}

随时随地退出app

如果你在第三个activity界面,这个时候想要退出App是非常不方便的,可以新建一个类来管理所有Activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ActivityController{
public static List<Activity> activities = new ArrayList<Activity>();

public static void addActivity(Activity ac){
activities.add(ac);
}

public static void removeActivity(Activity ac){
activities.remove(ac);
}

public static void finishAll(){
for(Activity ac : activities){
ac.finish();
}
}
}

这样只需要在BaseActivity的onCreate里面执行ActivityController的addActivity方法,即可把Activity添加进去,在BaseActivity的onDestroy方法中执行removeActivity,将其移除,在需要退出app的时候,只需要执行finishAll即可。

启动活动最佳写法

每个Activity中都写上启动自己的方法:

1
2
3
4
5
6
7
8
9

public class TestActivity extends BaseActivity{
public static void actionStart(Context context,String name,String sex){
Intent intent = new Intent(context,TestActivity.class);
intent.putExtra("name",name);
intent.putExtra("sex",sex);
context.startActivity(intent);
}
}

这样做的一个好处就是,启动这个activity所需要的参数一目了然,而无需阅读这个activity的源码就可以直接调用方法就能避免漏掉参数。

谢谢你的鼓励