2.1 从第一个工程开始
使用Android手机的读者都会使用手机中的App,但是这些App是如何运行的呢?可能大部分人都不知道。本节将以第1章创建的工程为例来讲解一个App是如何运行的,并介绍一个Android工程中的资源文件有哪些,同时对Activity进行简单的探究。
2.1.1 App是如何运行的
本节我们将一起分析一个App究竟是怎么运行起来的,这对于开发App很有帮助。以第1章所创建的工程为例,打开AndroidManifest.xml文件,从中找到如下代码:
这段代码表示对MainActivity这个Activity进行注册。没有在AndroidManifest.xml里注册的Activity是不能使用的。其中,intent-filter里的两行代码非常重要,<action android:name= "android.intent.action.MAIN" />和<category android:name="android.intent.category.LAUNCHER" />表示MainActivity是这个项目的主Activity,在手机上点击应用图标,首先启动的就是这个Activity。
那MainActivity具体又有什么作用呢?第1章中使用模拟机运行App的效果图中显示的其实就是MainActivity这个Activity。代码如下:
首先,MainActivity继承自AppCompatActivity类,而AppCompatActivity类继承自Activity类。Activity是Android系统提供的一个Activity基类,Android项目中所有的Activity都必须继承它才能拥有Activity的特性。然后,MainActivity中有一个onCreate()方法。onCreate()方法是一个Activity被创建时必定要执行的方法,其中只有两行代码,并且没有Hello world!字样。第1章效果图中显示的Hello world!是在哪里定义的呢?
其实Android程序的设计讲究逻辑和视图分离,因此是不推荐在Activity中直接编写界面的,更加通用的一种做法是先在布局文件中编写界面,然后在Activity中引入。在onCreate()方法的第二行调用了setContentView()方法,就是这个方法给当前的Activity引入了一个activity_main.xml布局,Hello world!就是在这里定义的。布局文件都是定义在res/layout目录下的,当展开layout目录时,会看到activity_main.xml这个文件。打开之后的代码如下:
后面会对布局进行详细讲解,现在只需要知道上面的代码中有一个TextView,这是Android系统提供的一个控件,是用于在布局中显示文字的即可。在TextView中还有hello world的字样,但是这并不是在效果显示图中显示的“Hello World!”。细心的读者会发现感叹号没了,大小写也不一样。
真正的“Hello world!”字符串也不是在布局文件中定义的。Android不推荐在程序中对字符串进行硬编码,更好的做法是先把字符串定义在res/values/strings.xml里,然后在布局文件或代码中引用。打开strings.xml,里面的内容如下:
“Hello world!”字符串就是定义在这个文件里的,并且字符串的定义都采用键值对的形式,Hello world!值对应了一个叫作hello_world的键,因此在activity_main.xml布局文件中通过引用hello_world这个键就能找到相应的值。
activity_main.xml布局文件中还有一个叫作app_name的键,可以通过修改app_name对应的值来改变应用程序的名称。那么到底是哪里引用了app_name这个键呢?在AndroidManifest.xml文件中有答案。读者可以自行研究。
2.1.2 项目中的资源
图2-1 res目录下的资源
展开res目录,里面的内容很多,很容易让人看得眼花缭乱,如图2-1所示。
简单来说,图2-1中的drawable文件夹是用来放图片的,所有以values开头的文件夹都是用来放字符串的,layout文件夹是用来放布局文件的,以mipmap开头的文件夹是用来放置应用图标的。
在开发过程中,引用这些资源有两种方式,以刚刚在strings.xml中找到的“Hello world!”字符串为例:
• 在代码中通过R.string.hello_world可以获得该字符串的引用。
• 在XML中通过@string/hello_world可以获得该字符串的引用。
基本的语法就是上面两种方式。对于第二种引用方式,读者经过上面的分析应该可以理解,而对于第一种方式就可能不太了解了。其中,R指的是Android中的R类。R类是将Android的资源文件存储为键值对的一个类,会将资源文件按照不同类型建立静态内部类,在内部类内部用键值对来存储。第一种引用方式就是在代码中引用R类下的静态内部类string类内键为“hello_world”的字符串。本应用中的Activity对R.layout.activity_main的调用就是同样的道理。特别提醒一下读者,此处的R类是系统自行生产并维护的,请勿修改,Android Studio为了避免开发者修改R类文件,特意把R类文件移出了。
res文件中的这些资源文件都是可以替换的。如果想要修改字符串,只需要到values文件夹下的strings.xml文件中修改键值对的值。如果想要修改图片资源只要修改drawable和 mipmap文件夹中的图片就可以。修改布局也是同样道理,修改layout文件夹下的布局文件就可以了。first项目的图标就是在AndroidManifest.xml中通过android:icon=“@drawable/ic_launcher”来指定的,ic_launcher图片在以mipmap开头的各个文件夹下。如果想要更换图标该怎么做呢?读者可以试一下。
2.1.3 理解Activity
通过前面两个部分对Android工程的分析,读者现在应该对一个Android应用的整体结构有了更加深入的了解,也会发现在一个Android工程中Activity处在一个极其重要的位置。Android应用的界面就是通过Activity调用res中的资源显示出来的。
Activity是Android组件中最基本也是最为常见用的四大组件(Activity、Service、Content Provider、BroadcastReceiver)之一。
一个Activity是一个应用程序组件,提供一个屏幕,可以供用户为了完成某项任务而进行交互,例如拨号、拍照、发送email、看地图。每一个Activity都被给予一个窗口,在上面可以绘制用户接口。窗口通常充满屏幕,也可以小于屏幕而浮于其他窗口之上。
一个应用程序通常由多个Activit组成,它们通常是松耦合关系。通常,一个应用程序中被指定为“main”activity的Activity是第一次启动应用程序的时候呈现给用户的那个Activity。每一个Activity都可以启动另一个Activity来完成不同的动作。每一次一个Activity启动,前一个Activity就停止了,但是系统保留Activity在一个栈上(Back Stack)。当一个新Activity启动时,它会被推送到栈顶,取得用户焦点。Back Stack符合简单“后进先出”原则,所以当用户完成当前Activity后单击Back按钮,它会被弹出栈(并且被摧毁),然后之前的Activity恢复。
当一个Activity因新的Activity启动而停止时,会通过Activity的生命周期回调函数通知这种状态转变。一个Activity可能会收到许多回调函数,这源于它自己的状态变化——无论系统创建它、停止它、恢复它、摧毁它——并且每个回调提供完成适合这个状态指定工作的机会。例如,当停止的时候,Activity应该释放任何大的对象,例如网络数据库连接。当Activity恢复时,可以重新获得必要的资源和恢复被中断的动作。这些状态转换都是Activity的生命周期部分(2.2节会详细论述)。
创建一个Activity,必须创建一个Activity的子类(或者一个Activity子类的子类),必须实现onCreate()方法。当创建Activity的时候系统会调用它。在我们的实现中,应该初始化Activity的基本组件。更重要的是,这里是我们必须调用setContentView()来定义Activity用户接口的地方。
Android提供大量预定义的View,用于设计和组建布局。Widget是一种给屏幕提供可视化(并且交互)元素的View,例如按钮、文件域、复选框或者仅仅是图像。Layouts是继承于ViewGroup的View,为子View提供特殊的布局模型,例如线程布局、格子布局或相关性布局。我们可以子类化View和ViewGroup类(或者存在的子类)来创建自己的Widget并应用到Activity布局中。
最普通的方法是使用View定义一个布局,一起和XML布局文件保存在程序资源里。这样就可以单独维护我们的用户接口设计(与定义Activity行为的代码无关)。可以使用setContentView()设置布局UI,传递资源布局的资源ID。
同时,必须在manifest文件中声明Activity,否则将不能被系统访问。声明格式如下:
一个<activity>元素能指定多种intent filters——使用<intent-filter>元素——声明其他应用程序时可以将其激活。当使用Android SDK工具创建一个新应用程序时,会自动创建存根Activity,包含一个intent filter,声明activity响应“main”动作,并且被放置在“launcher”分类中。
<action>元素指定这是一个“main”入口点。<category>元素指定这个Activity应该被列入系统应用程序列表中(为了允许用户启动这个Activity)。
如果希望应用程序自包含并且不希望其他应用程序激活它的activities,那么不需要任何intent filters。只有一个Activity应该有“main”动作和“launcher”分类,就像前面这个例子。不希望被其他应用程序访问时原Activities应该没有intent filters。
如果我们希望Activity从其他应用程序响应隐含的intents,就必须为这个Activity定义额外的intent filters。每一种你希望响应类型的intent都必须包含<intent-filter>、<action>元素,可选的有<category>元素或<data>元素。这些元素指定Activity能响应的intent的类型。
除了上面简述的Activity的生命周期,以及如何创建一个Activity、如何声明这个Activity之外,Activity还有一些常用的基本方法,也比较重要,如表2-1所示。
表2-1 Activity的常用方法
表2-1中只列出了Activity的基本方法,除此之外,Activity类还提供了与intent、service等相关的方法,等讲解相关知识点时再做阐述。