google官方文档地址:https://developer.android.google.cn/guide/components/activities#CoordinatingActivitiesandroid
ActivityActivity是最容易吸引用户的地方,它是一种能够包含用户界面的组件,主要用于和用户进行交互,好比执行拨打电话、拍摄照片、发送电子邮件或是查看地图等操做。一个应用程序中能够包含0个或者多个活动,但不包含任何活动的应用程序不多见,谁也不想让本身的应用永远没法被用户看到吧?网络
要建立活动,必须新建一个类继承自Activity(Android系统提供的一个活动基类,咱们项目中全部的活动都必须继承它或者它的子类才能拥有活动的特性),你须要知道,项目中的任何活动都应该重写Activity的onCreate()方法。这里建立咱们第一个活动命名为FirstActivity,布局文件名为first_layout。app
public class FirstActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
能够看到,onCreate()方法很是简单,就是调用了父类的onCreate()方法。后续,咱们还能够在其中加入不少本身的逻辑。ide
Android程序的设计讲究逻辑和视图分离最好是每个活动都可以对应着一个布局,布局就是用来显示界面内容的。经过XML文件的方式来编辑布局,这里建立一个名为first_layout.xml的文件:函数
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"
如今咱们来为这个布局添加一个按钮(Button)。布局
能够看到Button内部增长了几个属性。其中,android:id是给当前元素定义了一个惟一的标志符,以后能够在代码中对该元素进行操做;使用@+id/id_name这种语法在XML文件中定义了一个id;随后android:layout_width指定了当前元素的宽度这里的match_parent表示让当前元素和父元素同样宽;android:layout_height指定了当前元素的高度,这里使用了wrap_content表示当前元素的高度只要可以恰好包含里面的内容就能够了;android:text指定了元素中显示的文字内容。这些知识熟能生巧,如今看不明白没有关系,看的多了天然而然就会驾轻就熟。如今Button已经添加完毕。前面咱们说过,Android讲究逻辑与视图分离,那么定义好的视图怎么加载到逻辑中了?这里须要用到setContentView()方法来给当前的活动加载一个布局文件,在setContentView()方法中咱们通常会传入一个布局文件的id。项目中添加的任何资源都会在R文件中生成一个相应的资源id,所以,咱们刚刚建立的XML布局文件的id就已经添加到R文件中了,咱们只须要调用R.layout.first_layout就能够获得first_layout.xml布局的id,而后将这个值传入setContentView()方法中便可。动画
全部的活动都要在AndroidManifest.xml中进行注册才能生效,在Android Studio中编写时,它已经帮助咱们完成了注册的任务:ui
<activity android:name=".FirstActivity"
能够看到活动的注册放在application标签内部,经过activity标签来对活动进行注册。在this
如此一来,FirstActivity就成为咱们这个程序的主活动了,即点击桌面应用图标时首先打开的就是这个活动。另外须要注意,若是你的应用程序中没有声明任何活动做为主活动,这个程序仍然能够正常的安装,只是你没法在启动器中看到或者打开这个程序。这种程序通常都是做为第三方服务供其余应用在内部进行调用的,好比支付宝快捷支付服务。
那么,当有多个活动时,咱们如何才能从一个活动进入到另外一个活动呢?google
在启动其中点击应用的图标只会进入到该应用的主活动,那么怎样才能由主活动跳转到其余活动呢?是否是颇有意思、很好奇,那么咱们如今就来一块儿看看吧!
这里咱们再建立一个活动,命名为SecondActivity,布局文件名为second_layout。一样,咱们在second_layout中定义一个按钮,按钮上显示Button 2:
而后SecondActivity中的代码已经自动生成了一部分,咱们保持默认不变就好,以下所示:
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second_layout); } }
另外不要忘记,任何一个活动都须要在AndroidManifest.xml中注册,只不过这一工做Android Studio已经帮咱们完成了:
因为SecondActivity不是主活动,所以不须要配置
public class FirstActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first_layout); Button button1 = (Button) findViewById(R.id.button_1); button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(FirstActivity.this,SecondActivity.class); startActivity(intent); } }); } }
在活动中,能够经过findViewById()方法获取到在布局文件中定义的元素,这里咱们传入R.id.button_1,来获得按钮的实例,这个值是刚才在first_layout.xml中经过android:id属性指定的。findViewById()方法返回的是一个View对象,咱们须要向下转型将它转成Button对象。获得按钮的实例以后,咱们经过调用setOnClickListener()方法为按钮注册一个监听器,点击按钮时就会执行监听器中的onClick()方法。所以,功能固然要在onClick()方法中编写。
咱们首先构建了一个Intent,传入FirstActivity.this做为上下文,传入SecondActivity.class做为目标活动,这样咱们的意图就很清楚了,即在FirstActivity活动的基础上打开SecondActivity活动。而后经过startActivity()方法来执行这个Intent。
这时,运行程序,在FirstActivity活动中点击按钮便可进入SecondActivity活动中了,那么怎么返回上一个活动呢?只须要按下back键(手机的返回键)就能够销毁当前活动,从而回到上一个活动。
相比于显式Intent,隐式Intent则含蓄了许多,它并不明确指出咱们想要启动哪个活动,而是指定了一系列更为抽象的action和category等信息,而后交由系统去分析这个Intent,并帮助咱们找出合适的活动去启动。
那么,什么是合适的活动呢?简单来讲就是能够响应这个隐式Intent的活动。经过在
在
button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent("com.example.activitytest.ACTION_START"); startActivity(intent); } });
能够看到,咱们使用了Intent的另一个构造函数,直接将action的字符串传了进去,代表咱们想要启动可以响应com.example.activitytest.ACTION_START这个action的活动。那前面不是说要action和category同时匹配上才能相应吗?怎么没有看见哪里有指定category呢?这是由于android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent中。
这时,咱们便完成了和显式Intent同样的功能。
每一个Intent中只能指定一个action,但却能指定多个category。目前咱们的Intent中只有一个磨人的category,那么咱们再来增长一个吧。
修改FirstActivity中按钮的点击事件,代码以下所示:
button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent("com.example.activitytest.ACTION_START"); intent.addCategory("com.example.activitytest.MY_CATEGORY"); startActivity(intent); } });
能够调用Intent中的addCategory()方法来添加一个category,这里咱们指定了一个自定义的category,值为com.example.activitytest.MY_CATEGORY。
这时当你从新运行程序时,在FirstActivity的界面点击一下按钮,你会发现,程序崩溃了!别紧张,其实大多数的崩溃问题都是很好解决的,只要你善于分析。在logcat界面查看错误日志,你会发现错误信息中提醒咱们,没有任何一个活动能够响应咱们的Intent,为何呢?这是由于咱们刚刚在Intent中新增了一个category,而SecondActivity的
此时,再运行程序,就没有问题了。
掌握活动的生命周期对任何Android开发者来讲都很是重要,当你深刻理解活动的生命周期以后,就能够写出更加连贯流畅的程序,并在如何合理管理应用资源方面发挥得游刃有余。你的应用程序将会拥有更好的用户体验。
任务和返回栈
每一个活动在其生命周期中最多可能会有四种状态。
Activity类中定义了7个回调方法,覆盖了活动生命周期的每个环节,下面就来一一介绍这7个方法。
以上7个方法中除了onRestart()方法,其余都是两两相对的,从而又能够将活动分为3中生存期。
为了方便咱们更好的理解,Android官方提供了一张活动生命周期的示意图,以下:
图1 活动的生命周期
讲了这么多的理论知识,也是时候该实战一下了,下面咱们将经过一个实例,让你能够更加直观地体验活动的生命周期。
此次咱们不许备在ActivityTest这个项目的基础上修改了,而是新建一个项目。所以,首先关闭ActivityTest项目,点击导航栏File——>Close Project。而后再新建一个ActivityLifeCycleTest项目,新建项目的过程很简单,按照界面提示来就行,咱们容许Android Studio帮咱们自动建立活动和布局,这样能够省去很多工做,建立的活动名和布局名都使用默认值。
这样主活动就建立完成了,咱们还须要分别再建立两个子活动——NormalActivity和DialogActivity,下面一步步来实现。
右击com.example.activitylifecycletest包——>New——>Activity——>Empty Activity,新建NormalActivity,布局名为normal_layout。而后使用一样的方式建立DialogActivity,布局名为dialog_layout。
如今编辑normal_layout.xml文件,将里面的代码替换成以下内容:
在这个布局中,咱们就很是简单的使用了一个TextView,用于显示一行文字。而后编辑dialog_layout.xml文件,将里面的代码替换成以下内容:
两个布局文件的代码几乎没有区别,只是现实的文字不一样而已。
NormalActivity和DialogActivity中的代码咱们保持默认就好,不须要改动。
其实从名字上你就能够看出,这两个活动一个是普通的活动,一个是对话框式的活动。但是咱们并无修改活动的任何代码,两个活动的代码应该几乎是如出一辙的,在哪里有体现出将活动设成对话框式的呢?别着急,下面咱们立刻开始设置。修改AndroidManifest.xml的
这里是两个活动的注册代码,可是DialogActivity的代码有些不一样,咱们给它使用了一个android:theme属性,这是用于给当前活动指定主题的,Android系统内置有不少主题能够选择,固然咱们也能够定制本身的主题,而这里@style/Theme.AppCompat.Dialog则毫无疑问是让DialogActivity使用对话框式的主题。
接下来咱们修改activity_main.xml,从新定制主活动的布局,将里面的代码替换成以下内容:
能够看到,咱们在LinearLayout中加入了两个按钮,一个用于启动NormalActivity,一个用于启动DialogActivity。
最后修改MainActivity中的代码,以下所示:
package com.example.activitylifecycletest; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.Button; public class MainActivity extends BaseActivity { public 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(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this,NormalActivity.class); startActivity(intent); } }); startDialogActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, DialogActivity.class); startActivity(intent); } }); } @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"); } }
在onCreate()方法中,咱们分别为两个按钮注册了点击事件,点击第一个按钮会启动NormalActivity,点击第二个按钮会启动DialogActivity。而后再Activity的7个回调方法中分别打印了一句话,这样就能够经过观察日志的方式来更加直观地理解活动的生命周期。
如今运行程序,观察logcat中的打印日志
com.example.activitylifetest D/MainActivity:onCreate com.example.activitylifetest D/MainActivity:onStart com.example.activitylifetest D/MainActivity:onResume
能够看到,当MainActivity第一次被建立时会依次执行onCreate()、onStart()和onResume()方法。而后点击第一个按钮,启动NormalActivity,此时打印信息为:
com.example.activitylifetest D/MainActivity:onPause com.example.activitylifetest D/MainActivity:onStop
因为NormalActivity已经把MainActivity彻底遮挡,所以onPause()和onStop()方法都会获得执行。而后按下Back键返回MainActivity,打印信息为:
com.example.activitylifetest D/MainActivity:onRestart com.example.activitylifetest D/MainActivity:onStart com.example.activitylifetest D/MainActivity:onResume
因为以前MainActivity已经进入了中止状态,因此onRestart()方法会获得执行,以后又会依次执行onStart()和onResume()方法。注意此时onCreate()方法不会执行,由于MainActivity并无从新建立。
而后再点击第二个按钮,启动DialogActivity,此时观察打印信息为:
com.example.activitylifetest D/MainActivity:onPause
能够看到,只有onPause()方法获得了执行,onStop()方法并无执行,这是由于DialogActivity并无彻底遮挡住MainActivity,此时MainActivity只是进入了暂停状态,并无进入中止状态。相应地,按下Back键返回MainActivity也应该只有onResume()方法会获得执行。最后在MainActivity按下Back键退出程序,打印信息为:
com.example.activitylifetest D/MainActivity:onPause com.example.activitylifetest D/MainActivity:onStop com.example.activitylifetest D/MainActivity:onDestroy
依次执行onPause()、onStop()和onDestroy()方法,最终销毁MainActivity。
这样咱们就完整的体验了活动的生命周期,是否是理解的更加深入了?
前面咱们已经说过,当一个活动进入到中止状态,是有可能被系统回收的,那么想象如下场景:应用中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了中止状态,这个时候因为系统内存不足,将活动A回收掉了,而后用户按下Back键返回活动A,会出现什么状况呢?其实仍是会正常显示活动A,只不过这时并不会执行onRestart()方法,而是会执行活动A的onCreate()方法,由于活动A在这种状况下会被从新建立一次。
这样看上去好像一切正常,但是别忽略了一个重要的问题,活动A中是可能存在临时数据和状态的。打个比方,MainActivity中有一个文本输入框,如今你输入了一段文字,而后启动NormalActivity,这时MainActivity因为系统内存不足被回收掉了,过了一会你又点击了Back键回到了MainActivity,你会发现刚刚输入的文字所有都没了,由于MainActivity被从新建立了。
若是咱们的应用出现了这种状况,是会严重影响用户体验的,因此必需要想一想办法解决这个问题。Activity中提供了一个onSaveInstanceState()回调方法,这个方法能够保证在活动被回收以前必定会被调用,所以咱们能够经过这个方法来解决活动被回收时临时数据得不到保存的问题。
onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,好比可使用putString()方法保存字符串,使用putInt()方法保存整形数据,以此类推。每一个保存方法须要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。
在MainActivity中添加以下代码能够将临时数据进行保存:
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); String tempData = "Something you just typed"; outState.putString("data_key",tempData); }
数据是已经保存下来了,那么咱们应该在哪里进行恢复呢?细心的你也许早就发现,咱们一直使用的onCreate()方法其实也有一个Bundle类型的参数。这个参数在通常状况下都是null,可是若是在活动被系统回收以前有经过onSaveInstanceState()方法来保存数据的话,这个参数就会带有以前所保存的所有数据,咱们只须要再经过相应的取值方法将数据取出便可。
修改MainActivity的onCreate()方法,以下所示:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG,"onCreate"); setContentView(R.layout.activity_main); if (savedInstanceState != null) { String tempData = savedInstanceState.getString("data_key"); Log.d(TAG,tempData); } Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity); Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity); startNormalActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this,NormalActivity.class); startActivity(intent); } }); startDialogActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, DialogActivity.class); startActivity(intent); } }); }
取出值后在作相应的恢复操做就能够了,好比说将文本内容从新赋值到文本输入框上,这里咱们只是简单地打印一下。
活动的启动模式对咱们来讲是个全新的概念,在实际项目中咱们应该根据特定的需求为每个活动指定恰当的启动模式。启动模式一共分为4种,分别是standard、singleTop、singleTask和singleInstance,能够在AndroidManifest.xml中经过给
standard是活动默认的启动模式,在不进行显式指定的状况下,全部活动都会自动使用这种启动模式。所以,到目前为止咱们写过的全部活动都是使用的standard模式。在standard模式下,没当启动一个新的活动,它就会在返回栈中存在,每次启动都会建立该活动的一个新的实例。
可能在有些时候,你会以为standard模式不太合理。活动明明已经在栈顶了,为何再次启动的时候还要建立一个新的活动实例呢?别着急,这只是系统默认的一种启动模式而已,你彻底能够根据本身的须要进行修改,好比说是用singleTop模式。当活动的启动模式指定为singleTop模式,在启动活动时若是发现返回栈的栈顶已是该活动,则认为能够直接使用它,不会再建立新的活动实例。不过当活动并未处于栈顶位置时,这时再启动活动,仍是会建立新的实例的。
使用singleTop模式能够很好地解决重复建立栈顶活动的问题,可是若是活动并无处于栈顶的位置,仍是可能会建立多个活动实例的。那么有没有什么办法可让某个活动在整个应用程序的上下文中只存在一个实例呢?这就要借助singleTask模式来实现了。当活动的启动模式指定为singleTask,每次启动活动时系统首先会返回栈中检查是否存在该活动的实例,若是发现已经存在则直接使用该实例,并把在这个活动之上的全部活动通通出栈,若是没有发现就会建立一个新的活动实例。
这个模式比较特殊,不容易理解,你可能须要多花点精力来理解这个模式。不一样于其余3种模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动。那么这样作有什么意义呢?想象如下场景,假设咱们的程序中有一个活动是容许其余程序调用的,若是咱们想实现其余程序和咱们的程序能够共享这个活动的实例,应该如何实现呢?使用前面3种启动模式确定是作不到的,由于每一个应用程序都会有本身的返回栈,同一个活动在不一样的返回栈中入栈时必然是建立了新的实例。而使用singleInstance模式就能够解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,无论是哪一个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。