1. 什么是Activity
- Activity负责UI元素的加载与页面之前的跳转,代表了一个页面单元。
2. Activity的构成
- Activity的构成并不是一个Activity对象再加上一个布局那么简单,在Activity和开发人员设置的视图之间还隔着两层。
- 实际上视图会被设置给一个Window类,这个Window中含有一个DecorView,这个DecorView才是整个窗口的顶级视图。开发人员设置的布局会被设置到这个DecorView的mContentParent布局中。
- 也就是说Android中实际上内置了一些系统布局文件xml,我们在xml中定义的视图最终会被设置到这些系统布局的特定节点之下,这样就形成了整个的DecorView。
3. 隐式启动Activity
显式Intent指的是Intent的“意图”非常明显,指明了从哪个活动跳转到哪个活动。隐式Intent并不明确指出启动哪一个活动,而是指定一系列更为抽象的action和category信息,然后交由系统去分析这个intent,并帮我们找到合适的活动去启动。
默认的category的intent
- 创建FirstActivity,并且在配置文件中配置action和category:
public class FirstActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
}
}
说明:
- 在
标签中我们指明了当前活动可以响应 com.fkq.menu.First 这个action,而 标签则包含一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category。 - 只有
和 中的内容同时能够匹配上Intent指定的action和category时,这个活动才能响应该Intent。
2.修改MainActivity,进行跳转:
@InjectView(R.id.bt_main)
Button btMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
btMain.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.bt_main:
startActivity(new Intent("com.fkq.menu.First"));
}
}
});
}
说明:android.intent.category.DEFAULT 是默认的category,在调用startActivity方法的时候会自动将这个category添加到Intent中。
- 非默认的category的intent
每个Intent中只能指定一个action,但却能指定多个category。
1.修改配置文件的category,即添加一个:
2.修改MainActivity的startActivity部分代码:
Intent intent = new Intent("com.fkq.menu.First");
intent.addCategory("com.fkq.menu.my_category");
startActivity(intent);
4. 更多Intent用法
1. 简单示例
使用隐式Intent,不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。
比如应用程序中需要展示一个网页,只需要调用系统的浏览器来打开这个网页即可。
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
说明:
- 首先指定了Intent的action是Intent.ACTION_ViEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW.
- 然后通过Uri.parse方法,将一个网址字符串解析承一个Uri对象。
- 再调用Intent的setData方法将这个Uri对象传递进去。
再次说明:
setData方法接收一个Uri对象,主要用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse方法中解析产生的。
2. 配置data标签
我们可以在
- android:scheme 用于指定数据的协议部分,如上例中的http部分。
- android:host 用于指定数据的主机名部分,如上例中的www.baidu.com部分。
- android:port 用于指定数据的端口部分,一般紧跟在主机名之后。
- android:path 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
- android:mimeType 用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
注意:只有标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。不过一般在标签中都不会指定过多的内容,如上面浏览器示例中,其实只需要指定android:scheme为http,就可以响应所有的http协议的Intent了。
修改配置文件如下:
说明:标签中通过android:sheme指定了数据的协议必须是http协议,这样就和浏览器一样,能够响应一个打开网页的Intent了。
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
除了http协议外,我们还可以指定很多其他协议,比如geo表示显示地理位置、tel表示拨打电话。
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
说明:
- 首先指定Intent的action是Intent.ACTION_DIAL,这又是Android系统的内置动作。
- 然后在data部分指定了协议是tel,号码是10086。
5. 向下一个活动传递数据
发送:
String data = "hello world";
Intent intent = new Intent(MainActivity.this,FirstActivity.class);
intent.putExtra("mydata",data);
startActivity(intent);
说明:通过putExtra方法传递一个字符串,接收两个参数,第一个参数是键,用于从Intent中取值,第二个参数才是真正要传递的数据。
接收:
Intent intent = getIntent();
String data = intent.getStringExtra("mydata");
Toast.makeText(this, "传递过来的值是" + data, Toast.LENGTH_SHORT).show();
说明:通过getIntent方法获取到用于启动FirstActivity的Intent,然后调用getStringExtra方法来获取传递的数据。如果是整型数据,则使用getIntExtra方法,如果是布尔数据,则使用getBooleanExtra方法。
6. 返回数据给上一个活动
Activity中有一个startActivityForResult方法用于启动活动,期望在活动销毁的时候能够返回一个结果给上一个活动。
- 示例:MainActivity跳转到FirstActivity,FirstActivity销毁返回数据给MainActivity
MainActivity:
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_trans:
Intent intent = new Intent(MainActivity.this, FirstActivity.class);
startActivityForResult(intent, 1);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if (resultCode == RESULT_OK){
String returnedData = data.getStringExtra("data_return");
Log.e("MainActivity",returnedData);
}
}
}
说明:
- startActivityForResult方法接收两个参数,第一个参数还是intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
- 使用startActivityForResult方法启动SecondActivity,请求码只要是唯一值就可以了,这里传入了1。
FirstActivity:
Button button = (Button) findViewById(R.id.bt_re_data);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.bt_re_data:
Intent intent = new Intent();
intent.putExtra("data_return","hello");
setResult(RESULT_OK,intent);
finish();
break;
}
}
});
说明:
- 在FirstActivity里面给按钮添加点击事件,并添加返回数据的逻辑。
- 构建一个Intent仅仅用于传递数据而已,紧接着把要传递的数据存放在Intent中,然后调用setResult方法,专门用于向上一个活动返回数据的。
- setResult方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则把带有数据的intent传递回去,然后调用finish方法销毁当前活动。
- 由于使用startActivityForResult方法来启动FirstActivity的,在FirstActivity被销毁之后会回调MainActivity的onActivityResult方法,因此我们需要在MainActivity中重写这个方法来得到返回的数据。
- onActivityResult方法带有三个参数,第一个参数requestCode,就是启动活动的时候传入的请求码。第二个参数resultCode,就是我们在返回数据时传入的处理结果。第三个参数data,就是携带着返回数据的Intent。
- 由于一个活动中可能调用startActivityForResult方法去启动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult这个方法中,因此首先要做得就是检查requestCode的值来判断数据来源。确定数据是从FirstActivity返回的之后,再通过resultCode的值来判断处理结果是否成功。最后从data中取值并打印出来,这样就完成了向上一个活动返回数据的工作。
疑问:
假如FirstActivity中不用按钮来销毁活动,而是通过点击Back键,那如何处理呢?很简单:在FirstActivity中重写onBackPressed方法来解决这个问题。
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return","hello");
setResult(RESULT_OK,intent);
finish();
}
7. 活动的生命周期
返回栈
Android是用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈。
每当我们启动一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。每当我们按下Back键或调用finish方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。
系统总是会显示处于栈顶的活动给用户。
活动状态
- 运行状态:当一个活动处于栈顶时,这个活动就处于运行状态。
- 暂停状态:当一个活动不处于栈顶,但是依然可见的时候,这个活动就进入了暂停状态。如:对话框后面的活动。
- 停止状态:当一个活动不处于栈顶,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量。当其他地方需要内存时,活动可能会被系统回收。
- 销毁状态:当一个活动从返回栈中移除后就变成了销毁状态。
- 由于可见的活动被回收,用户体验不好,所以系统最不愿意回收运行和暂停状态的活动,当内存紧张的时候,会优先回收销毁状态和停止状态的活动。
活动的生存期
- Activity类中定义了7个回调方法,覆盖了活动生命周期的每一个环节:
- onCreate:
活动第一次被创建的时候调用,所以在这个活动中完成初始化操作:加载布局,绑定事件等。 - onStart:
活动由不可见变为可见的时候调用。 - onResume:
活动准备好和用户进行交互的时候调用。 - onPause:
这个活动在系统准备去启动或者恢复另一个活动的时候调用。通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但是这个活动执行速度一定要快,不然会影响新的栈顶活动的使用。 - onStop:
这个方法在活动完全不可见的时候调用。与onPause方法主要区别是:如果启动的新活动是一个对话框式的活动,那么onPause方法会得到执行,而onStop方法并不会。
当活动执行了onStop方法,此时另一个优先级更高的程序需要内存,可能会杀掉进程,这时再启动的话,会先执行onCreate方法。 - onDestory:
活动被销毁前调用。 - onRestart:
活动由停止状态变为运行状态之前调用,即重新启动时调用,意思就是当一个活动执行了onStop方法,但是并没有执行onDestory方法时,如果被重新启动,会执行onRestart方法,然后会执行onStart方法,而不会重新执行onCreate方法。
- 以上活动又分为3种生存期
- 完整生存期:活动在onCreate方法和onDestroy方法之间所经历的,就是完整生存期。
- 可见生存期:活动在onStart和onStop方法之间所经历的,就是可见生存期。在这个生存期内,onStart方法对资源进行加载,而在onStop方法对资源进行释放,合理管理对用户可见的资源。
- 前台生存期:活动在onResume方法和onPause方法之间就是前台生存期。
体验活动的生命周期
1.创建MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_main);
Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);
startNormalActivity.setOnClickListener(this);
startDialogActivity.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.start_normal_activity:
startActivity(new Intent(this, NormalActivity.class));
break;
case R.id.start_dialog_activity:
startActivity(new Intent(this, DialogActivity.class));
break;
}
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
}
2.创建DialogActivity和NormalActivity:
public class DialogActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dialog);
}
}
public class NormalActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_normal);
}
}
3.修改配置文件:
注意:由于类继承的是AppCompatActivity,所以主题应该是AppCompat里面的,否则会报错。
说明:针对MainActivity
- 进入应用程序,MainActivity第一次被创建会依次执行onCreate、onStart、onResume方法
- 启动NormalActivity,MainActivity会执行onPause和onStop方法,因为NormalActivity把MainActivity完全遮掩住了。
- 按下Back返回MainActivity,MainActivity会执行onRestart方法,然后执行onStart和onResume方法,因为之前MainActivity已经进入了停止状态,所以onRestart方法;之所以onCreate方法没有执行,因为MainActvity并没有重新创建。
- 启动DialogActivity,MainActivity只会执行onPause方法,onStop方法并没有执行,因为DialogActivity并没有完全遮掩住MainActivity,MainActivity只是进入了暂停状态,并没有进入停止状态。相应地,按下Back键返回MainActivity也应该只有onResume方法会得到执行。
- 在MainActivty页面按下Back键推出程序,依次执行onPause、onStop、onDestory方法。
8. 活动被回收了怎么办
当活动被销毁以后,可能会销毁掉活动中原有的数据,比如EditText中输入的内容。为了解决这个问题,Android提供了一个onSaveInstanceState回调方法,这个方法可以保证在活动被回收之前一定会被调用。
onSaveInstanceState方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString保存字符串,使用putInt方法保存整形数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。
@Override
public void onSaveInstanceState(Bundle outState) {
String tempdata = "something you just typed";
outState.putString("data_key",tempdata);
super.onSaveInstanceState(outState);
Log.e(TAG,"此时被调用");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState!=null){
String tempdata = savedInstanceState.getString("data_key");
Toast.makeText(this,"tempdata::"+tempdata,Toast.LENGTH_SHORT).show();
}
}
说明:
- 利用手机的横竖屏切换,可以完美做模拟,先调用onSaveInstanceState,然后调用onCreate。
- onCreate方法中有一个Bundle类型的参数。这个参数在一般情况下都是null,但是如果在活动被系统回收之前有通过onSaveInstanceState方法来保存数据的话,这个参数就会带有之前所保存的全部数据。
9. 活动的启动模式和应用场景
启动模式有4种,分别是standard、singleTop、singleTask和singleInstance,可以在配置文件中通过给
standard
不管活动是否已经在返回栈中存在,每次启动都会创建一个新的实例,并且位于栈顶。
singleTop
在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
应用场景:下拉栏通知界面点击进入一个页面的情景,避免了因为多次启动导致的需要返回多次的情况。
singleTask
每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动示例。
应用场景: 例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。之前打开过的页面,打开之前的页面就ok,不再新建.
singleInstance
启动一个新的返回栈来管理这个活动。假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的案例,使用之前的三种模式是做不到的,因为每个应用程序都有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这个种模式下会有一个单独的返回栈来管理活动,不管是哪个应用程序来访问这个活动,都共用同一个返回栈,也就解决了共享活动实例的问题。
栗子:FirstActivity跳转到SecondActivity,然后在SecondActivity进入到THirdActivity。SecondActivity单独在一个返回栈,FirstActivity和THirdActivity在一个返回栈。点击Back,ThirdActivity直接返回到FirstActivity,再按下Back键又会返回到SecondActivity,再按下Back键会推出程序。
原理:FirstA和ThridA存放在同一个返回栈,当在ThridA的界面按下Back键,ThridAcitivy会从返回栈出栈,FirstA成为栈顶活动显示在界面上。在FirstA界面上按下Back键,当前的返回栈空了就显示了另一个返回栈的栈顶活动,即SecondActivity,按下Back,所有的返回栈空了,退出程序。
应用场景: 比如通话页面、闹铃提醒页面.
10. 动态设置活动的启动模式
- FLAG_ACTIVITY_NEW_TASK
举例说明:经过测试,并没有开启一个额外的任务栈,并且会重新创建。(如果测试不正确,后期会修改)
- FLAG_ACTIVITY_SINGLE_TOP
举例说明:栈中有A B C D四个活动,在D中用这个Flag启动D,现在栈中有A B C D 四个活动,D不会重新创建,会回调onNewIntent方法。
- FLAG_ACTIVITY_CLEAR_TOP
举例说明:栈中有A B C D 四个活动,在D中用这个Flag启动B,此时栈中只有A B ,B会重新创建。
- FLAG_ACTIVITY_NO_HISTORY
举例说明:栈中有A B C D 四个活动,在D中用这个Flag启动B,B会重新创建,此时栈中有A B C D 新B,然后从新B打开C,现在栈中有A B C D C 五个活动。
- FLAG_ACTIVITY_BROUGHT_TO-FRONT
举例说明:我们有A,A打开B,B打开C,C打开D,D然后以这个Flag启动B, 此时栈的情况就是A,C,D,B。B不会重新创建,回调onNewIntent方法。
- FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK
举例说明: 我看郭霖博客,这两个Flag可以让Activity不会重新创建,但是经过我测试,依然会重新创建。(如果测试不正确,后期会修改)
11. 知晓当前是在哪一个活动
创建BaseActivity,让FirstActivity、SecondActivity、ThirdActivity继承BaseAcitify,点击进入FirstActivity、SecondActivity、ThirdActivity,Log就会打印出当前活动的名称。BaseActivity代码如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
12.随时随地退出程序
- 新建ActivityCollector:
public class ActivityCollector {
public static List activities = new ArrayList<>();
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();
}
}
}
}
2.新建BaseActivity:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
3.新建FirstActivity继承BaseActivity:
ActivityCollector.finishAll();
android.os.Process.killProcess(android.os.Process.myPid());
说明:
- FirstActivity继承BaseActivity,启动FirstActivity,FirstActivity这个类就会被保存到集合里面。
- 如果FirstActivit被系统内存回收销毁后,集合会把FirstActivit移除。
- 想退出程序,只需清空集合即可。
- 为了保证程序完全退出,杀掉当前进程:killProcess方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过myPid()方法来获得当前程序的进程id。
13. 启动活动的最佳写法
假设SecondActivity中需要用到两个非常重要的字符串参数,在启动MainActivity的时候必须要传递过来:
Intent intent = new Intent(this,MainActivity.class);
intent.putExtra("param1","data1");
intent.putExtra("param2","data2");
startActivity(intent);
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);
}
MainActivity.actionStart(this,"data1","data2");