第5章 Activity与Intent
知识导图
知识导图
本章案例效果
什么是Activity Activity是Android应用中重要组成部分之一,如果把一个Android应用看成是一个网站的话,那么一个Activity就相当于该网站的一个具体网页。Android应用开发的一个重要组成部分就是开发Activity。 Activity是一种应用程序组件,该组件提供了一个屏幕,用户通过 与这个屏幕交互可完成一定的功能,例如打电话,拍照,发送邮件或者 查看地图等。 每一个Activity都提供了一个用于显示它的用户界面的窗口。这个 窗口通常会充满整个屏幕,但有可能比这个屏幕更小或者是漂浮在其他 窗口之上。
什么是Activity 应用程序通常由多个彼此之间松耦合的Activity组成。通常,在一个应用程序中,有且仅有一个Activity被指定为主Activity。当应用程序第一次启动的时候,该Activity会显示给用户。 每个Activity都可以启动其它的Activity用于执行不同的操作(功能)。当一个新的Activity启动的时候,先前的那个Activity就会停止,但是系统在堆栈中仍保存该Activity。 当一个新的Activity启动时,它将会被压入栈顶,并获得用户焦点。堆栈遵循后进先出的队列原则,因此,当用户使用完当前的Activity并按Back键时,该Activity将从堆栈中取出(并销毁),然后先前的Activity恢复并获取焦点。
创建和配置Activity 创建自己的Activity需要继承Activity基类,在不同的应用场景下,有时也可继承Activity的子类,例如ListActivity、TabActivity。 创建一个Activity需要实现一个或多个方法,其中最常见的就是实现onCreate(Bundle status)方法,该方法将会在Activity创建时被回调,该方法调用Activity的setContentView(View view)方法来显示要展示的View。 为了管理应用程序界面中的各个控件,可调用Activity的findViewById(int id)方法来获取程序界面中的组件,接下来即可修改该组件的属性和方法以满足我们的需求。 Android应用要求所有应用程序组件(Activity、Service、 ContentProvider、BroadcastReceiver)都必须进行注册。
创建和配置Activity 为<application …/>元素添加<activity…/>子元素即可注册Activity。注册时,主要有以下几个属性: name:指定该Activity的实现类的类名; icon:指定该Activity对应的图标; label:指定该Activity的标签。 配置Activity时通常还可以指定一个或多个<intent-filter…/>元素,该元素用于指定该Activity可响应的条件。 上述配置中,只有name属性是必须的,而其它属性或标签元素都是可选的。
启动Activity 一个Android应用通常都会包含多个Activity,但只有一个Activity会作为程序的入口(当该Android应用运行时将会自动启动并执行该Activity)。而应用中的其他Activity,通常都由入口Activity启动,或由入口Activity启动的Activity启动。 启动其它Activity的方法如下: startActivity(Intent intent):启动其他Activity; startActivityForResult(Intent intent,int requestCode):程序将会得到新启动Activity的结果(通过重写onActivityResult(…)方法来获取),requestCode参数代表启动Activity的请求码。这个请求码的值由开发者根据业务自行设置,用于标识请求来源。
启动Activity 上面两个方法,都需要传入一个Intent类型的参数,该参数是对你 所需要启动的Activity的描述,既可以是一个确切的Activity类,也可 以是所需要启动的Activity的一些特征,然后由系统查找符合该特征 的Activity。 如果有多个Activity符合该要求时,系统将会以下拉列表的形式列出 所有的Activity,然后由用户选择具体启动哪一个,这些Activity既可 以是本应用程序的,也可以是其他应用程序的。
关闭Activity finish():结束当前Activity; finishActivity(int requestCode):结束以startActivityForResult(Intent intent,int requestCode)方法启动的Activity。 注意:大部分情况下,不建议显式调用这些方法关闭Activity。因为Android系统会为我们管理Activity的生命周期,调用这些方法可能会影响用户的预期体验。因此,只有当你不想用户再回到当前Activity的时候才去关闭它。
Activity的生命周期 整个生命周期 可见生命周期 前台生命周期 前台生命周期 可见生命周期 整个生命周期
Activity的生命周期 Activity的三种状态 Resumed:已恢复状态,此时Activity位于前台,并且获得用户焦点,这种状态通常也叫运行时状态; Paused:暂停状态,Activity失去用户焦点,但该Activity仍是可见的,即该Activity仍存在于内存中,并能维持自身状态和记忆信息,且维持着和窗口管理器之间的联系。但是,当系统内存极度缺乏的时候可能杀死该Activity。 Stopped:停止状态,该Activity完全被其他Activity所覆盖,该Activity仍存在于内存中,能维持自身状态和记忆信息,但它和窗口管理器之间已没有了联系。当系统需要内存时,随时可以杀死该Activity。
Activity的生命周期 整个生命周期 可见生命周期 从onCreate()开始到onDestroy()结束。Activity在onCreate()设置所有的“全局”状态,例如界面的布局文件,在onDestory()释放所有的资源。例如:关闭下载进程。 可见生命周期 从onStart()开始到onStop()结束。可以看到Activity在屏幕上,尽管有可能不在前台,不能和用户交互。onStart()和onStop()方法都可以被多次调用,因为Activity随时可在可见和隐藏之间转换,例如:可以在onStart中注册一个监听器来监听数据变化导致UI的变动,当不再需要显示时候,可以在onStop()中注销它。
Activity的生命周期 前台生命周期 从onResume()开始到onPause()结束。在这段时间里,该Activity处于所有Activity的最前面,和用户进行交互。Activity可以经常性地在resumed和paused状态之间切换,例如:当设备准备休眠时,当一个 Activity处理结果被分发时,当一个新的Intent被分发时。所以在这些方法中的代码应该属于非常轻量级的。
Activity的生命周期 案例:模拟Activity生命周期中方法的调用。重写Activity生命周期中的方法,方法调用时,在控制台打印出相应的信息,根据信息查看方法调用顺序。 程序运行后,系统会依次调用:onCreate→onStart→onResume,此时MainActivity就处于运行时状态了;退出时,系统依次调用onPause→onStop→onDestroy方法。 onCreate→onStart→onResume→ onPause →onStop→【onRestart→onStart→onResume→ onPause→onStop→】onDestroy。【】中间的部分可执行零到多次,即可见生命周期循环。 onCreate→onStart→onResume→onPause→【onResume→ onPause→】onStop→onDestroy。 【】中间的部分可执行一到多次,即前台生命周期循环。
问题与讨论 1、当MainActivity正在运行时,若此时直接按Home键,返回到桌面,MainActivity是否还存在?控制台会打印什么消息? 仍然存在,不会调用onDestroy方法。 2、返回到原来的Activity都是使用返回键,如果我们在新启动的Activity中添加一个按钮,单击按钮后,跳转到原来的Activity,这样做与单击返回键有区别吗? 通过按钮跳转到原Activity只是表面现象,实际上系统重新创建了一个Activity,即此时在Activity堆栈中包含两个Activity对象。而通过返回键操作,则是销毁当前的Activity,从而使上一个Activity获取焦点,重新显示在前台,它是不断地从堆栈中取出Activity。
Activity间交换数据 实际应用中,仅仅有跳转还是不够的,往往还需要进行通信,即数据的传递。在Android中,主要是通过Intent对象来完成这一功能的,Intent对象就是它们之间的信使。 数据传递的方向有两个: 从当前Activity传递到新启动的Activity 从新启动的Activity返回结果到当前Activity
startActivityForResult获取返回结果操作: 在当前Activity中重写onActivityResult(int requestCode ,int resultCode,Intent intent)方法,其中requestCode代表请求码,resultCode代表返回的结果码; 在启动的Activity执行结束前,调用该Activity的setResult(int resultCode,Intent intent)方法,将需要返回的结果写入到Intent中。 startActivityForResult执行过程 当前Activity调用startActivityForResult(Intent intent,int requestCode)方法启动一个符合Intent要求的Activity; 新启动的Activity执行它相应的方法,并将执行结果通过setResult(int resultCode,Intent intent)方法写入Intent; 当该Activity执行结束后,调用原来Activity的onActivityResult(int requestCode ,int resultCode,Intent intent),判断请求码和结果码是否符合要求,从而获取Intent里的数据。
启动Activity并返回结果 为什么要请求码和结果码? 因为在一个Activity中可能存在多个控件,每个控件都有可能添加相应的事件处理,调用startActivityForResult()方法,从而就有可能打开多个不同的Activity处理不同的业务。但这些Activity关闭后,都会调用原来Activity的onActivityResult(int requestCode, int resultCode, Intent intent)方法。通过请求码,我们就知道该方法是由哪个控件所触发的,通过结果码,我们就知道返回的数据来自于哪个Activity。
启动Activity并返回结果 Intent保存数据的方法: Intent提供了多个重载的方法来存放额外的数据,主要格式如下。 putExtras(String name, Xxx data):其中Xxx表示数据类型,向Intent中放入Xxx类型的数据,例如int、long、String等; 此外还提供了一个putExtras(Bundle data)方法,该方法可用于存放一个数据包,Bundle类似于Java中的Map对象,存放的是键值对的集合,可把多个相关数据放入同一个Bundle中,Bundle提供了一系列的存入数据的方法,方法格式为putXxx(String key, Xxx data),向Bundle中放入int、long等各种类型的数据。同时还提供了相应的getXxx(String key)方法,从Bundle中取出各种类型的数据。
用户注册案例
用户注册案例 运行 主界面分析
用户注册案例 用户注册程序结构分析
Fragment介绍 Fragment是Android3.0引入的新API,可以把它理解为Activity中的片段或者子模块。Fragment拥有自己的生命周期,也可以接受自己的输入事件。但Fragment必须被嵌入到Activity中使用,Fragment的生命周期会受它所在的Activity的生命周期的控制。例如,当Activity暂停时,该Activity内的所有Fragment都会暂停,而当Activity处于运行状态时,可以独立的操作每一个Fragment,例如添加、删除等。 一个Activity中可包含多个Fragment;反过来,一个Fragment也可以被多个Activity复用。 在Fragment中,可通过getActivity()方法获取它所在的Activity;在Activity中,可通过getFragmentManager()方法得到Fragment管理器,然后调用它的findFragmentById()等 方法获取Fragment。
Fragment的创建 与Activity类似,创建自定义的Fragment必须继承系统提供的Fragment基类或者它的子类,然后可根据需要实现它的一些方法。Fragment中的回调方法与Activity的回调方法非常类似,主要包含onAttach()、onCreate()、onCreateView()、onActivityCreated()、onStart()、onResume()、onPause()、onStop()、onDestroyView()、onDestroy()、onDetach()等。为了控制Fragment的显示,通常需要重写onCreateView()方法,该方法返回的View将作为该Fragment显示的View控件,当Fragment绘制界面时将会回调该方法。在该方法中,通常是通过LayoutInflater类的inflate()方法将布局文件转换成一个View对象。
Fragment的创建 Fragment创建完成后,还需要将其嵌入到Activity中去,将Fragment添加到Activity中有以下两种方式: 在布局文件中使用<fragment…/>标签添加Fragment,通过该标签的android:name属性指定Fragment的实现类,属性值为完整的包名+类名; 在Java代码中,通过getFragmentManager()方法获取FragmentManager对象,然后调用其beginTransaction()方法开启事务,得到FragmentTransaction对象,再调用该对象的add()方法来添加Fragment,最后调用commit()方法提交事务。
Fragment示例 设计右图所示界面效果。界面中包含两个按钮:登录和注册,单击登录按钮时在下方显示登录界面,单击注册按钮时在下方显示注册界面。整个功能效果在一个Activity内实现,登录和注册分别放在单独的Fragment中。
练习 练习 1 2 编写Activity类,重写它的生命周期方 法,在Activity间进行跳转和传值,观察不 同情况下,方法调用的顺序。 实现注册功能 1 2
测试题 1.以下方法不属于Activity生命周期的回调方法的是( )。 A)onStart() B)onCreate() C)onPause() D)onFinish() 2.以下方法中,在Activity的生命周期中不一定被调用的是( )。 A)onCreate() B)onStart() C)onPause() D)onStop() 3.对于Activity中一些重要资源与状态最好在哪个方法中进行保存( )。 A)onPause() B)onCreate() C)onResume() D)onStart() 4.配置Activity时,下列那一项是必不可少的( )。 A)android:name属性 B)android:icon属性 C)android:label属性 D)<intent-filter.../>元素 5.下列选项哪个不是启动Activity的方法 ( )。 A) startActivity() B) goToActivity() C) startActivityForResult() D) startActivityFromChild()
Intent详解 什么是Intent? 在Android应用中,三种核心组件:Activity、Service、 BroadcastReceiver彼此是独立的,它们之所以可以互相调用、协调工 作,最终形成一个具有一定功能的Android应用,主要是通过Intent对象 协助来完成的。 “Intent”中文翻译为“意图”,是对一次即将运行的操作的抽象描述, 包括操作的动作、动作涉及数据、附加数据等,Android系统则根据 Intent的描述,负责找到对应的组件,并将Intent传递给调用的组件,完 成组件的调用。因此,Intent在这里起着媒体中介的作用,专门提供组件 互相调用的相关信息,实现调用者与被调用者之间的解耦。
Intent详解 例如,我们想通过联系人列表查看某个联系人的详细信息,点击某 个联系人后,希望能够弹出此联系人的详细信息。 为了实现这个目的,联系人Activity需要构造一个Intent,这个 Intent 用于告诉系统,我们要做“查看”动作,此动作对应的查看对 象是“具体的某个联系人”,然后调用startActivity (Intent intent), 将构造的Intent 传入,系统会根据此Intent中的描述,到 AndroidManifest.xml中找到满足此Intent 要求的Activity,最终 传入Intent,对应的Activity则会根据此Intent 中的描述,执行相应 的操作。 Intent实际上就是一系列信息的集合,既包含对接收该Intent的 组件有用的信息,如即将执行的动作和数据,也包括对Android系统 有用的信息,如处理该Intent的组件的类型以及如何启动一个目标 Activity。
Intent详解 Intent构成 Component name(组件名):指定Intent的目标组件名称,即组件的类名。 通常 Android会根据Intent中包含的其他属性信息进行查找,比如action、data/type、category,最终找到一个与之匹配的目标组件。 但如果component这个属性有指定的话,将直接使用它指定的组件,而不再执行上述查找过程。 指定了这个属性以后,Intent的其他所有属性都是可选的。Intent的Component name属性需要接受一个ComponentName对象,创建ComponentName对象时需要指定包名和类名,从而可唯一确定一个组件类,这样应用程序即可根据给定的组件类去启动特定的组件。
Intent详解 代码如下: 等价于: 在启动的组件中,通过以下语句获取相关的信息: ComponentName comp=new ComponentName(Context con,Class class); Intent intent=new Intent(); Intent.setComponent(comp); 等价于: Intent intent=new Intent(Context con,Class class); 在启动的组件中,通过以下语句获取相关的信息: ComponentName comp=getIntent().getComponent(); comp.getPackageName();//获取组件的包名 comp.getClassName();//获取组件的类名
Intent详解 action(动作): Action代表该Intent所要完成的一个抽象“动作”,这个动作具体 由哪个组件来完成,Action这个字符串本身并不管。 例如Android提供的标准Acton:Intent.ACTION_VIEW,它只表 示一个抽象的查看操作,但具体查看什么,启动哪个Activity来查看, 它并不知道(这取决于Activity的<intent-filter.../>配置,只要某个 Activity的<intent-filter.../>配置中包含了该ACTION_VIEW,该 Activity就有可能被启动)。 Intent类中定义了一系列的Action常量,具体的可查阅Android SDK→reference中的Android.content.intent类,通过这些常量我 们能调用系统为我们提供的功能。
AndroidManifest.xml配置名称 常用Action Action名称 AndroidManifest.xml配置名称 描述 ACTION_MAIN android.intent.action.MAIN 作为一个程序的入口,不需要接收数据 ACTION_VIEW android.intent.action.VIEW 用于数据的显示 ACTION_DIAL android.intent.action.DIAL 调用电话拨号程序 ACTION_EDIT android.intent.action.EDIT 用于编辑给定的数据 ACTION_PICK android.intent.action.PICK 从特定的一组数据之中进行数据的选择操作 ACTION_RUN android.intent.action.RUN 运行数据 ACTION_SEND android.intent.action.SEND 调用发送短信程序 ACTION_GET_CONTENT android.intent.action.GET_CONTENT 根据指定的Type来选择打开操作内容的Intent ACTION_CHOOSER android.intent.action.CHOOSER 创建文件操作选择器
Intent详解 category(类别): 执行动作的组件的附加信息。例如LAUNCHER_CATEGORY 表示 Intent 的接受者应该在Launcher中作为顶级应用出现;而 ALTERNATIVE_CATEGORY表示当前的Intent是一系列的可选动作中的一 个,这些动作可以在同一块数据上执行。同样的,在Intent类中定义了一 些Category常量。 一个Intent对象最多只能包括一个Action属性,程序可调用的 setAction(String str)方法来设置Action属性值;但一个Intent对象可以 包含多个Category属性,程序可调用Intent的addCategory(String str) 方法来为Intent添加Category属性。 当程序创建Intent时,该Intent默认启动Category属性值为 Intent.CATEGORY_DEFAULT常量的组件。
Intent详解 Intent类中部分Category常量表 Category常量 对应字符串 简单描述 CATEGORY_DEFAULT android.intent.category.DEFAULT 默认的Category CATEGORY_BROWSABLE android.intent.category. BROWSABLE 指定该Activity能被浏览器安全调用 CATEGORY_TAB android.intent.category. TAB 指定Activity作为TabActivity的Tab页 CATEGORY_LAUNCHER android.intent.category. LAUNCHER Activity显示在顶级程序列表中 CATEGORY_HOME android.intent.category. HOME 设置该Activity随系统启动而运行
Intent详解 data(数据): content://com.android.contacts/contacts/1 Data属性通常用于向Action属性提供操作的数据。不同的Action通常需要携带不同的数据,例如如果Action是ACTION_CALL,那么数据部分将会是tel:需要拨打的电话号码。 Data属性接受一个URI对象,一个URI对象通常通过如下形式的字符串来表示: content://com.android.contacts/contacts/1 tel:13876523467 上面所示的两个字符串的冒号前面大致指定了数据的类型,冒号后面的是数据部分。因此一个合法的URI对象既可决定操作哪种数据类型的数据,又可指定具体的数据值。
数据举例 Android中部分数据表 操作类型 Data(Uri)格式 范例 浏览网页 http://网页地址 http://www.baidu.com 拨打电话 tel:电话号码 tel:15970142365 发送短信 smsto:短信接收人号码 smsto: 13621384455 查找SD卡文件 file:///sdcard/文件或目录 file:///sdcard/mypic.jpg 显示地图 geo:坐标,坐标 geo:31.899533,-27.036173
Intent详解 type(数据类型):显式指定Intent的数据类型(MIME)。一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。通常来说当Intent不指定Data属性时Type属性才会起作用,否则Android系统将会根据Data属性来分析数据的类型,因此无须指定Type属性。 extras(附加信息):是其它所有附加信息的集合,以键值对形式保存所有的附加信息。使用extras可以为组件提供扩展信息。比如,如果要执行“发送电子邮件”这个动作,可以将电子邮件的标题、正文等保存在extras里,传给电子邮件发送组件。
Intent详解 <intent-filter.../>元素是AndroidManifest.xml文件中<activity…/>元素的子元素,该子元素用于配置该Activity所能“响应”的Intent。 <intent-filter…/>元素里通常可包含如下子元素: 0-N个<action…/>子元素 0-N个<category…/>子元素 0-1个<data…/>子元素 当<activity…/>元素里的<intent-filter…/>子元素里包含多个<action…/>子元素(相当于指定了多个字符串)时,就表明该Activity能响应Action属性值为其中任意一个字符串的Intent。
Intent详解 Android如何解析Intent? 直接(显式)Intent:指定了component 属性的Intent(调用setComponent(ComponentName)或者setClass(Context, Class)来指定)。通过指定具体的组件类,通知应用启动对应的组件。 间接(隐式)Intent:没有指定component 属性的Intent。这 些Intent 需要包含足够的信息,这样系统才能根据这些信息,在所有 的可用组件中,确定满足此Intent 的组件。
Intent解析 Android系统中Intent解析的判断方法如下: 1、如果Intent指明了Action,则目标组件的IntentFilter的Action列表中就必须包含有这个Action,否则不能匹配; 2、如果Intent没有提供Type,系统将从Data中得到数据类型。和Action一样,目标组件的数据类型列表中必须包含Intent的数据类型,否则不能匹配; 3、如果Intent中的数据不是content类型的URI,而且Intent也没有明确指定它的Type类型,将根据Intent中数据的Scheme进行匹配,例如“http:”或“tel:”。同上,Intent的Scheme必须出现在目标组件的Scheme列表中; 4、如果Intent指定了一个或多个Category,这些类别必须全部出现在组件的类别列表中。比如Intent中包含了两个类别:LAUNCHER_CATEGORY和ALTERNATIVE_CATEGORY,解析得到的目标组件必须至少包含这两个类别。
Intent解析 Android系统中Intent解析的匹配过程如下: 1、Android系统把所有应用程序包中的Intent过滤器集合在一起,形成一个完整的Intent过滤器列表; 2、在Intent与Intent过滤器进行匹配时,android系统会将列表中所有Intent过滤器的“动作”和“类别”与Intent进行匹配,任何不匹配的Intent过滤器都将被过滤掉,没有指定“动作”的Intent请求可以匹配任何的Intent过滤器。 3、把Intent数据URI的每个子部与Intent过滤器的<data>标签中的属性进行匹配,如果<data>标签指定了协议、主机名、路径名或MIME类型,那么这些属性都要与Intent的URI数据部分进行匹配,任何不匹配的Intent过滤器均被过滤掉; 4、如果Intent过滤器的匹配结果多于一个,则可以根据在<intent-filter>标签中定义的优先级标签来对Intent过滤器进行排序,优先级最高的Intent过滤器将被选择。
Intent详解 注意: 理论上说, 一个intent对象如果没有指定category, 它应该能通过任意的category 测试。有一个例外: android把所有传给startActivity()的隐式intent看做至少有一个category: “android.intent.category.DEFAULT”。 因此, 想要接受隐式intent的activity,必须在intent filter中加入“android.intent.category.DEFAULT”。(“android.intent.action.MAIN” 和“android.intent.category.LAUNCHER”的intent filter例外,它们不需要"android.intent.category.DEFAULT"。)
电话拨号器 实现下图所示调用系统拨号功能:在文本框中输入要拨打的号码,单击拨打按钮,此时弹出对话框,提示用户请求调用系统拨号功能权限。如果拒绝将弹出一个Toast信息,提示用户没有权限无法执行拨号功能;如果同意,则进入拨号界面。
电话拨号器 private void dail(){ Intent intent=new Intent();→创建Intent对象 intent.setAction(Intent.ACTION_CALL);→指定动作为拨号 intent.setData(Uri.parse("tel:"+numberText.getText())); →将字符串转换成Uri对象 startActivity(intent);→启动相应组件 } setAction()方法用于指定所需要执行的动作,setData()用于指定相关的数据。
电话拨号器 注意: 拨号功能对用户来说是隐私,调用时需要申请相关的权限。 在Android6.0之前只需要在清单文件(AndroidManifest.xml)中<application>标签外添加如下权限声明即可:<uses-permission android:name="android.permission.CALL_PHONE"/>。 Android6.0之后,对权限进行了优化,提出了运行时权限,不要求安装时授权,而是当需要使用某些权限时,提示用户授权,用户拒绝后只是影响这一功能的使用,不会影响其他功能的使用,非常灵活、方便。
测试题 1.Android 中下列属于Intent的作用的是( )。 A)实现应用程序间的数据共享 B)是一段长的生命周期,没有用户界面的程序,可以保持应用在后台运行,而不 会因为切换页面而消失 C)可以实现界面间的切换,可以包含动作和动作数据,组件间调用的纽带 D)处理一个应用程序整体性的工作 2.Intent的以下哪个属性通常用于在多个Action之间进行数据交换?( )。 A) Category B) Component C) Data D) Extra 3.简要描述Intent的主要组成部分,各部分的含义。
练习 编写一个简单的浏览器应用,包含一个文本输入框和一个浏览按钮,文本输入框用于输入网址,单击浏览按钮即可调用浏览器打开该网页。程序运行效果如下图所示。(提示:调用系统的Action:ACTION_VIEW,添加访问网络权限:<uses-permission android:name="android.permission.INTERNET"/>)