第2章 活动的启动模式

第2章 活动的启动模式




2.5 活动的启动模式

活动的启动模式对你来说应该是个全新的概念,在实际项目中我们应该根据特定的需求为每个活动指定恰当的启动模式。启动模式一共有四种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性来选择启动模式。下面我们来逐个进行学习。


2.5.1 standard

standard是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。因此,到目前为止我们写过的所有活动都是使用的standard模式。经过上一节的学习,你已经知道了Android是使用返回栈来管理活动的,在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
我们现在通过实践来体会一下standard模式,这次还是准备在ActivityTest项目的基础上修改,首先关闭ActivityLifeCycleTest项目,打开ActivityTest项目。

修改FirstActivity中onCreate()方法的代码,如下所示:


@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	Log.d("FirstActivity", this.toString());
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	setContentView(R.layout.first_layout);

	Button button1 = (Button) findViewById(R.id.button_1);
	button1.setOnClickListener(new OnClickListener() {
		@Override
		public void onClick(View v) {
			Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
			startActivity(intent);
		}
	});
}


代码看起来有些奇怪吧,在FirstActivity的基础上启动FirstActivity。从逻辑上来讲这确实没什么意义,不过我们的重点在于研究standard模式,因此不必在意这段代码有什么实际用途。另外我们还在onCreate()方法中添加了一行打印信息,用于打印当前活动的实例。

现在重新运行程序,然后在FirstActivity界面连续点击两次按钮,可以看到LogCat中打印信息如图2.30所示。


图 2.30
从打印信息中我们就可以看出,每点击一次按钮就会创建出一个新的FirstActivity实例。此时返回栈中也会存在三个FirstActivity的实例,因此你需要连按三次Back键才能退出程序。
standard模式的原理示意图,如图2.31所示。



图 2.31


2.5.2 singleTop

可能在有些情况下,你会觉得standard模式不太合理。活动明明已经在栈顶了,为什么再次启动的时候还要创建一个新的活动实例呢?别着急,这只是系统默认的一种启动模式而已,你完全可以根据自己的需要进行修改,比如说使用singleTop模式。当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
我们还是通过实践来体会一下,修改AndroidManifest.xml中FirstActivity的启动模式,如下所示:


<activity
	android:name=".FirstActivity"
	android:launchMode="singleTop"
	android:label="This is FirstActivity" >
	<intent-filter>
		<action android:name="android.intent.action.MAIN" />
		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>


然后重新运行程序,查看LogCat会看到已经创建了一个FirstActivity的实例,如图2.32所示。


图 2.32

但是之后不管你点击多少次按钮都不会再有新的打印信息出现,因为目前FirstActivity已经处于返回栈的栈顶,每当想要再启动一个FirstActivity时都会直接使用栈顶的活动,因此FirstActivity也只会有一个实例,仅按一次Back键就可以退出程序。
不过当FirstActivity并未处于栈顶位置时,这时再启动FirstActivity,还是会创建新的实例的。下面我们来实验一下,修改FirstActivity中onCreate()方法的代码,如下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	Log.d("FirstActivity", this.toString());
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	setContentView(R.layout.first_layout);

	Button button1 = (Button) findViewById(R.id.button_1);
	button1.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
		startActivity(intent);
		}
	});
}


这次我们点击按钮后启动的是SecondActivity。然后修改SecondActivity中onCreate()方法的代码,如下所示:


protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	Log.d("SecondActivity", this.toString());
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	setContentView(R.layout.second_layout);

	Button button2 = (Button) findViewById(R.id.button_2);
	button2.setOnClickListener(new OnClickListener() {
		@Override
		public void onClick(View v) {
			Intent intent = new Intent(SecondActivity.this, FirstActivity.class);
			startActivity(intent);
		}
	});
}


我们在SecondActivity中的按钮点击事件里又加入了启动FirstActivity的代码。现在重新运行程序,在FirstActivity界面点击按钮进入到SecondActivity,然后在SecondActivity界面点击按钮,又会重新进入到FirstActivity。
查看LogCat中的打印信息,如图2.33所示。


图 2.33


可以看到系统创建了两个不同的FirstActivity实例,这是由于在SecondActivity中再次启动FirstActivity时,栈顶活动已经变成了SecondActivity,因此会创建一个新的FirstActivity实例。现在按下Back键会返回到SecondActivity,再次按下Back键又会回到FirstActivity,再按一次Back键才会退出程序。
singleTop模式的原理示意图,如图2.34所示。


图 2.34



2.5.3 singleTask

使用singleTop模式可以很好地解决重复创建栈顶活动的问题,但是正如你在上一节所看到的,如果该活动并没有处于栈顶的位置,还是可能会创建多个活动实例的。那么有没有什么办法可以让某个活动在整个应用程序的上下文中只存在一个实例呢?这就要借助singleTask模式来实现了。当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
我们还是通过代码来更加直观地理解一下。修改AndroidManifest.xml中FirstActivity的启动模式:
<activity
	android:name=".FirstActivity"
	android:launchMode="singleTask"
	android:label="This is FirstActivity" >
	<intent-filter>
		<action android:name="android.intent.action.MAIN" />
		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>


然后在FirstActivity中添加onRestart()方法,并打印日志:
@Override
	protected void onRestart() {
	super.onRestart();
	Log.d("FirstActivity", "onRestart");
}

最后在SecondActivity中添加onDestroy()方法,并打印日志:


@Override
protected void onDestroy() {
	super.onDestroy();
	Log.d("SecondActivity", "onDestroy");
}

现在重新运行程序,在FirstActivity界面点击按钮进入到SecondActivity,然后在SecondActivity界面点击按钮,又会重新进入到FirstActivity。
查看LogCat中的打印信息,如图2.35所示。


图 2.35


其实从打印信息中就可以明显看出了,在SecondActivity中启动FirstActivity时,会发现返回栈中已经存在一个FirstActivity的实例,并且是在SecondActivity的下面,于是SecondActivity会从返回栈中出栈,而FirstActivity重新成为了栈顶活动,因此FirstActivity的onRestart()方法和SecondActivity的onDestroy()方法会得到执行。现在返回栈中应该只剩下一个FirstActivity的实例了,按一下Back键就可以退出程序。
singleTask模式的原理示意图,如图2.36所示。


图 2.36



2.5.4 singleInstance

singleInstance模式应该算是四种启动模式中最特殊也最复杂的一个了,你也需要多花点功夫来理解这个模式。不同于以上三种启动模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。那么这样做有什么意义呢?想象以下场景,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?使用前面三种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
为了帮助你可以更好地理解这种启动模式,我们还是来实践一下。修改AndroidManifest. xml中SecondActivity的启动模式:
<activity
	android:name=".SecondActivity"
	android:launchMode="singleInstance" >
	<intent-filter>
		<action android:name="com.example.activitytest.ACTION_START" />
		<category android:name="android.intent.category.DEFAULT" />
		<category android:name="com.example.activitytest.MY_CATEGORY" />
	</intent-filter>
</activity>


我们先将SecondActivity的启动模式指定为singleInstance,然后修改FirstActivity中onCreate()方法的代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	Log.d("FirstActivity", "Task id is " + getTaskId());
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	setContentView(R.layout.first_layout);

	Button button1 = (Button) findViewById(R.id.button_1);
	button1.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
		startActivity(intent);
		}
	});
}

在onCreate()方法中打印了当前返回栈的id。然后修改SecondActivity中onCreate()方法的代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	Log.d("SecondActivity", "Task id is " + getTaskId());
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	setContentView(R.layout.second_layout);

	Button button2 = (Button) findViewById(R.id.button_2);
	button2.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
		startActivity(intent);
	}
	});
}


同样在onCreate()方法中打印了当前返回栈的id,然后又修改了按钮点击事件的代码,用于启动ThirdActivity。最后修改ThirdActivity中onCreate()方法的代码:


@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	Log.d("ThirdActivity", "Task id is " + getTaskId());
	requestWindowFeature(Window.FEATURE_NO_TITLE);
	setContentView(R.layout.third_layout);
}

仍然是在onCreate()方法中打印了当前返回栈的id。现在重新运行程序,在FirstActivity界面点击按钮进入到SecondActivity,然后在SecondActivity界面点击按钮进入到ThirdActivity。

查看LogCat中的打印信息,如图2.37所示。


图 2.37


可以看到,SecondActivity的Task id不同于FirstActivity和ThirdActivity,这说明SecondActivity确实是存放在一个单独的返回栈里的,而且这个栈中只有SecondActivity这一个活动。
然后我们按下Back键进行返回,你会发现ThirdActivity竟然直接返回到了FirstActivity,再按下Back键又会返回到SecondActivity,再按下Back键才会退出程序,这是为什么呢?其实原理很简单,由于FirstActivity和ThirdActivity是存放在同一个返回栈里的,当在ThirdActivity的界面按下Back键,ThirdActivity会从返回栈中出栈,那么FirstActivity就成为了栈顶活动显示在界面上,因此也就出现了从ThirdActivity直接返回到FirstActivity的情况。然后在FirstActivity界面再次按下Back键,这时当前的返回栈已经空了,于是就显示了另一个返回栈的栈顶活动,即SecondActivity。最后再次按下Back键,这时所有返回栈都已经空了,也就自然退出了程序。
singleInstance模式的原理示意图,如图2.38所示。


图 2.38



2.6 活动的最佳实践

你已经掌握了关于活动非常多的知识,不过恐怕离能够完全灵活运用还有一段距离。虽然知识点只有这么多,但运用的技巧却是多种多样。所以,在这里我准备教你几种关于活动的最佳实践技巧,这些技巧在你以后的开发工作当中将会非常受用。


2.6.1 知晓当前是在哪一个活动

这个技巧将教会你,如何根据程序当前的界面就能判断出这是哪一个活动。可能你会觉得挺纳闷的,我自己写的代码怎么会不知道这是哪一个活动呢?很不幸的是,在你真正进入到企业之后,更有可能的是接手一份别人写的代码,因为你刚进公司就正好有一个新项目启动的概率并不高。阅读别人的代码时有一个很头疼的问题,就是你需要在某个界面上修改一些非常简单的东西,但是你半天找不到这个界面对应的活动是哪一个。学会了本节的技巧之后,这对你来说就再也不是难题了。
我们还是在ActivityTest项目的基础上修改。首先需要新建一个BaseActivity继承自Activity,然后在BaseActivity中重写onCreate()方法,如下所示:


public class BaseActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.d("BaseActivity", getClass().getSimpleName());
	}
}


我们在onCreate()方法中获取了当前实例的类名,并通过Log打印了出来。
接下来我们需要让BaseActivity成为ActivityTest项目中所有活动的父类。修改FirstActivity、SecondActivity和ThirdActivity的继承结构,让它们不再继承自Activity,而是继承自BaseActivity。虽然项目中的活动不再直接继承自Activity了,但是它们仍然完全继承了Activity中的所有特性。
现在重新运行程序,然后通过点击按钮分别进入到FirstActivity、SecondActivity和ThirdActivity的界面,这时观察LogCat中的打印信息,如图2.39所示。


图 2.39
现在每当我们进入到一个活动的界面,该活动的类名就会被打印出来,这样我们就可以时时刻刻知晓当前界面对应的是哪一个活动了。


2.6.2 随时随地退出程序

如果目前你手机的界面还停留在ThirdActivity,你会发现当前想退出程序是非常不方便的,需要连按三次Back键才行。按Home键只是把程序挂起,并没有退出程序。其实这个问题就足以引起你的思考,如果我们的程序需要一个注销或者退出的功能该怎么办呢?必须要有一个随时随地都能退出程序的方案才行。
其实解决思路也很简单,只需要用一个专门的集合类对所有的活动进行管理就可以了,下面我们就来实现一下。
新建一个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();
			}
		}
	}
}


在活动管理器中,我们通过一个List来暂存活动,然后提供了一个addActivity()方法用于向List中添加一个活动,提供了一个removeActivity()方法用于从List中移除活动,最后提供了一个finishAll()方法用于将List中存储的活动全部都销毁掉。
接下来修改BaseActivity中的代码,如下所示:

public class BaseActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.d("BaseActivity", getClass().getSimpleName());
		ActivityCollector.addActivity(this);
	}
	@Override
	protected void onDestroy() {
		super.onDestroy();
		ActivityCollector.removeActivity(this);
	}
}


在BaseActivity的onCreate()方法中调用了ActivityCollector的addActivity()方法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity中重写onDestroy()方法,并调用了ActivityCollector的removeActivity()方法,表明将一个马上要销毁的活动从活动管理器里移除。
从此以后,不管你想在什么地方退出程序,只需要调用ActivityCollector.finishAll()方法就可以了。例如在ThirdActivity界面想通过点击按钮直接退出程序,只需将代码改成如下所示:

public class ThirdActivity extends BaseActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.d("ThirdActivity", "Task id is " + getTaskId());
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.third_layout);

		Button button3 = (Button) findViewById(R.id.button_3);
		button3.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				ActivityCollector.finishAll();
			}
		});
	}
}


当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出。


2.6.3 启动活动的最佳写法

启动活动的方法相信你已经非常熟悉了,首先通过Intent构建出当前的“意图”,然后调用startActivity()或startActivityForResult()方法将活动启动起来,如果有数据需要从一个活动传递到另一个活动,也可以借助Intent来完成。
假设SecondActivity中需要用到两个非常重要的字符串参数,在启动SecondActivity的时候必须要传递过来,那么我们很容易会写出如下代码:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);

这样写是完全正确的,不管是从语法上还是规范上,只是在真正的项目开发中经常会有对接的问题出现。比如SecondActivity并不是由你开发的,但现在你负责的部分需要有启动SecondActivity这个功能,而你却不清楚启动这个活动需要传递哪些数据。这时无非就有两种办法,一个是你自己去阅读SecondActivity中的代码,二是询问负责编写SecondActivity的同事。你会不会觉得很麻烦呢?其实只需要换一种写法,就可以轻松解决掉上面的窘境。

修改SecondActivity中的代码,如下所示:


public class SecondActivity extends BaseActivity {
	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);
	}
	……
}


我们在SecondActivity中添加了一个actionStart()方法,在这个方法中完成了Intent的构建,另外所有SecondActivity中需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储到Intent中,最后调用startActivity()方法启动SecondActivity。

这样写的好处在哪里呢?最重要的一点就是一目了然,SecondActivity所需要的数据全部都在方法参数中体现出来了,这样即使不用阅读SecondActivity中的代码,或者询问负责编写SecondActivity的同事,你也可以非常清晰地知道启动SecondActivity需要传递哪些数据。另外,这样写还简化了启动活动的代码,现在只需要一行代码就可以启动SecondActivity,如下所示:


button1.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
	}
});


养成一个良好的习惯,给你编写的每个活动都添加类似的启动方法,这样不仅可以让启动活动变得非常简单,还可以节省不少你同事过来询问你的时间。


2.7 小结与点评

真是好疲惫啊!没错,学习了这么多的东西不疲惫才怪呢。但是,你内心那种掌握了知识的喜悦感相信也是无法掩盖的。本章的收获非常多啊,不管是理论型还是实践型的东西都涉及了,从活动的基本用法,到启动活动和传递数据的方式,再到活动的生命周期,以及活动的启动模式,你几乎已经学会了关于活动所有重要的知识点。另外在本章的最后,还学习了几种可以应用在活动中的最佳实践技巧,毫不夸张地说,你在Android活动方面已经算是一个小高手了。
不过你的Android旅途才刚刚开始呢,后面需要学习的东西还很多,也许会比现在还累,一定要做好心理准备哦。总体来说,我给你现在的状态打满分,毕竟你已经学会了那么多的东西,也是时候该放松一下了。自己适当控制一下休息的时间,然后我们继续前进吧!



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值