Android4开发入门经典 之 第二部分:Android应用的核心基础
Android应用中的组件
Application Components
Android应用中最主要的组件是:
1:Activity:活动,活动是最基本的Andorid应用程序组件,应用程序中,一个活动通常就是一个单独的屏幕。
2:Service:服务,服务是运行在后台的,通常是具有一段较长生命周期且没有用户界面的程序。比较好的一个例子就是播放歌曲的媒体播放器,就算你导航到其它屏幕时音乐也还是在播放。
3:Content provider:内容提供者,内容提供者实现了一组标准的方法,从而能够让不同的应用之间可以相互保存或读取彼此的内容,当然都需要实现自己的内容提供器。
4:Broadcast receiver:广播接收者,你可以使用BroadcastReceiver来让你的应用对一个外部的事件做出响应。比如:当电话呼入时,数据网络可用时。
Intent基本概念
Intent是什么
是一个简单的消息对象,它表示程序想做某事的“意图”,可以用来”激活”Activity、Service或BroadCast Receiver。
比如想要从一个Activity启动另外一个Activity,就可以这么写:
java代码:
- Button btnToT2 = (Button)this.findViewById(R.id.btn_toT2);
- btnToT2.setOnClickListener(new OnClickListener(){
- public void onClick(View v) {
- Intent in = new Intent();
- in.setClass(HelloWorldActivity.this, T2.class);
- HelloWorldActivity.this.startActivity(in);
- }
- });
Intent对象能包含下面六种数据:
1:Component name,组件名称:来处理Intent的组件类的全路径名称。可以通过setComponent()、setClass()、setClassName()方法来进行设置,通过 getComponent()方法进行读取。
2:Action,动作:一个描述要做什么事情的字符串。在Intent类里面预定义了。
3:Data,数据:用来描述Action对应的具体的数据和数据的MIME Type,比如要打电话,那么就是电话数据的uri,通常是“tel://”后面加上号码。如果是其他的数据,通常是“content://”类型的uri。
相应的setData()方法仅仅用来设置URI,而setType()方法仅仅设置MIME Type,setDataAndType()可以同时设置,另外可以通过相应的get方法获取值。
4:Category,类别:用来描述组件应该如何处理Intent的附加信息。在Intent类中有Category具体的预定义。可以通过addCategory()、removeCategory()和getCategories()来操作Category。
5:Extras,额外数据:传递给组件的附加数据,通常是key-value对。可以通过putExtras()和getExtras()方法来操作数据
6:Flags,标记:各种各样的类别标记,用来描述如何装载Activity,以及装载后如何处理这些Activity。
Intent通常被分成两种:显式的和隐式的
1:显式的Intent:直接根据组件的名称来进行指派,通常用作应用内部的消息传递机制,比如启动service或其他Activity等。
2:隐式的Intent:不用根据组件的名称来进行指派,通常用作启动其他应用的组件。比如接收广播消息,Android系统需要寻找到最合适处理这个消息的应用,通常会使用Intent Filter来实现。
Intent Filter基本概念
Intent Filter是什么
用来描述一个Activities、Services、BroadCast Receivers能够操作哪些intent。它们都可以包含一到多个Intent Filter。
nIntent Filter能包含的数据通常只有三种:
action、data(包括URI和类型)、category。
Intent Filter通常在AndroidManifest.xml文件里面配置使用
一个简要的示例如:
java代码:
- <intent-filter>
- <action android:name="android.intent.action.GET_CONTENT" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
- </intent-filter>
Intent基本示例
发短信的示例
java代码:
- Uri uri = Uri.parse(“smsto:发送的号码");
- Intent in = new Intent(Intent.ACTION_SENDTO,uri);
- in.putExtra("sms_body", "发个短信玩");
- Hello.this.startActivity(in);
打电话的示例:
java代码:
- Uri uri = Uri.parse(“tel:发送的号码");
- Intent in = new Intent(Intent.ACTION_CALL,uri);
- Hello.this.startActivity(in);
- 记得在AndroidManifest.xml文件中进行授权,<uses-permission android:name="android.permission.CALL_PHONE" />,这些权限可以在Manifest.permission里面查到。
资源
什么是资源
资源是程序外部文件,也就是非代码的文件,它被代码使用并在编译时编入应用程序。Android支持不同类型的资源文件,包括XML,PNG以及JPEG 。
资源类型
Android平台上存在不同的资源类型,大部分资源类型以及默认的存放包路径,在上一节课的时候已经作了介绍。
创建资源
通常,你可以通过三种类型的文件来创建资源:XML文件(除位图以及原数据文件),位图文件(对于图片)以及原始数据(其它类型,例如声音文件,等等)。
Android支持的单位
1:px 像素:屏幕上的点
2:in 英寸:长度单位
3:mm 毫米:长度单位
4:pt 磅:1/72英寸
5:dp 与密度无关的像素:一种基于屏幕密度的抽象单位,在每英寸160点的显示器上,1dp = 1px
6:dip 等同于dp
7:sp 与刻度无关的像素:与dp类似,但是可以根据用户字体大小的首选项来进行缩放
建议:始终使用sp作为文字大小的单位,dip或dp作为其他元素的单位。
在代码里面使用资源
资源最终会被编译成APK文件,Android会创建一个包装类,命名为R,它指向你程序中所有的资源。这个类包含很多子类。每一种都是Android 支持的,同时,编译后会产生一个资源文件。每个类提供一个或多个编译后资源的标识符,里面包含了字符串,布局文件(全屏或者部分屏幕),以及图像资源。这样你就能在你的代码里使用这些资源了。
注意:R引用的是资源ID,是一个int值,如果需要文本型的数据,应该使用getText方法,比如: this.getText(R.string. app_name);
从其他资源类里引用资源
一个在属性或者资源里提供的数值可以被指向一个具体的资源,可以通过一个引用来使用这些资源。比如HelloWorld里面引用strings.xml里面的文本。
基本语法:@[<package_name>:]<resource_type>/<resource_name>
用‘@’的前缀是说明资源引用,后面的文本是资源的名字,前面的示例没有指定包,因为是在自己的包里引用资源。
引用系统资源
要使用系统资源,你需要使用android包,比如:
java代码:
- <Button
- ……
- android:textColor="@android:color/holo_red_light"
- />
- 系统带的资源数据很多,可以通过android.R和它包含的类去查看。
- 当然,也可以访问自己的color定义,比如在res/values/建立一个colors.xml,然后定义自己要使用的颜色,示例如下:
- <resources>
- <color name="my_color">#770000ff</color>
- </resources>
- 那么上面那句话就修改成:
- android:textColor="@color/my_color"
主题属性引用
另一种资源数值允许你引用当前主题属性值。这种属性引用只能被用于特殊的资源类以及XML属性中;它允许你根据现在主题风格将你定制的UI变得更标准化,而不用使用大量的具体数值。
语法为:?[<package_name>:][<resource_type>/]<resource_name>
示例如下:
java代码:
- <EditText id="text"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:textColor="?android:textColorSecondary"
- android:text="@string/hello_world"
- />
注意除来我们将前缀'?'代替了'@',其他非常像资源引用。当你使用这个标记,系统会自动查找你提供的属性的名字 -- 资源工具知道肯定会有资源属性相符合,你不需要使用资源标识符到主题里去寻找相应的数据而不是直接使用原数据。
对不同的语言和设置提供不同的资源
你可以根据产品界面语言以及硬件配置设置不同的资源。注意,虽然你可以包含不同的字串,布局以及其他资源,但开发包(SDK)不会给你显式的方法去指定不同的资源去加载。Android检测你的硬件以及位置信息选择合适的设置去加载。用户可以到设备上的设置界面去选择不同的语言。
要包含不同的资源,在同一目录下创建并行的文件夹,在每个文件夹后加上合适的名字,这个名字能表明一些配置信息(如语言,原始屏幕等等)。
比如:
java代码:
- res/
- drawable/
- icon.png
- background.png
- drawable-hdpi/
- icon.png
- background.png
配置限定名称(或修饰语)
Android系统可以配置的限定名称的详细列表,参见Dev Guide/Application Resources /Providing Resources 的 table 2。
这里有几点说明:
1:你可以在一个资源类型后面,添加任意多个限定名称,名称之间以破折号分开,但是他们的先后顺序必须和上面table2文档里面的顺序保持一致
java代码:
- Wrong: drawable-hdpi-port/
- Correct: drawable-port-hdpi/
2:限定名称大小写敏感,也不能出现两个一样的限定名称,即使大小写不一样
3:一个式子里同一个类型修饰语中只有一个值是有效的
4:你可以指定多个参数去定义不同的配置,但是参数必须是table2里的
5:所有目录,无论是限制的,还是不限制的,只要在 res/ 目录下,是不能嵌套的
Android如何找到最合适的资源
Android将会挑出哪些基本资源文件在运行时会被使用,这依靠当前的配置。举例来说吧,假如有如下的drawable资源文件夹:
java代码:
- drawable/
- drawable-en/
- drawable-fr-rCA/
- drawable-en-port/
- drawable-en-notouch-12key/
- drawable-port-ldpi/
- drawable-port-notouch-12key/
- 假设当前的设备配置为:
- Locale = en-GB
- Screen orientation = port
- Screen pixel density = hdpi
- Touchscreen type = notouch
- Primary text input method = 12key
资源的国际化
1:创建缺省的本地资源
其实就是默认的那些资源文件夹和文件名称的定义
2: Android的资源国际化的实现方式,其实就是依靠设置修饰符来区分,如果某个语言没有找到相应的资源文件夹,那么就使用缺省的本地资源
3:要注意各个文件夹只是内容的表现形式不一样,但是资源的名称和内容应该是一样的
4:在Java里面引用的时候,是无需考虑修饰符的,照样是通过R来引用
5:如果你希望在Java里面访问到本地的一些信息,可以这么写:
String locale = context.getResources().getConfiguration().locale.getDisplayName();
如果在Activity里面,context就是this了。
安全和权限
安全架构
1:Android安全学中的一个重要的设计点是在默认情况下应用程序没有权限执行对其它应用程序、操作系统或用户有害的操作。如读/写用户的隐私数据(如联系人信息),读/写其它应用程序的文件,执行网络访问,保持设备活动等。
2:应用程序的进程是一个安全的沙箱。它不能干扰其它应用程序,除非在它需要添加原有沙箱不能提供的功能时明确声明权限。
应用程序签名
所有的Android应用程序(.apk文件)必须通过一个证书的签名,此证书的私钥必须被开发者所掌握。这个证书的标识是应用程序的作者。这个证书不需要通过证书组织的签署:Android应用程序对于使用自签署的证书是完全允许的和特别的。
这个证书仅仅被用于与应用程序建立信任关系,不是为了大规模的控制应用程序可否被安装。最重要的方面是通过确定能够访问原始签名权限和能够共享用户ID的签名来影响安全。
用户标识和文件访问
1:Android的操作系统linux是一个多用户的操作系统,每个应用就相当于一个用户
2:系统缺省的为每个应用分配一个ID,用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性。
3:某个应用程序所创建的任何文件,都不能被其他应用程序读写
4:每个进程有自己的虚拟机,因此应用的代码能够与其他应用独立开来进行运行
5:每个应用程序都在其自己的linux进程中运行,硬件禁止一个进程访问其他进程的内存
6:有些时候,有可能需要两个应用共享同样的Linux user ID,以便他们能互相访问文件,资源等,这要求两个应用必须有相同的签名。可以设置每一个包中的AndroidManifest.xml文件中的manifest 标签属性sharedUserId,使得他们拥有相同的用户ID。
使用权限
一个基本的Android应用程序没有与其相关联的权限,意味着它不能做任何影响用户体验或设备中的数据的有害操作。要利用这个设备的保护特性,在你的应用程序需要时,你必须在AndroidManifest.xml文件中包含一个或更多的<uses-permission> 标签来声明此权限。
比如接收短信的权限:
<uses-permission android:name="android.permission.RECEIVE_SMS" />
可以在Manifest.permission找到Android系统提供的权限定义。
应用、任务、进程和线程
应用
一个android 包 (简称 .apk ) ,里面包含应用程序的代码以及资源。这是一个应用发布,用户能下载并安装到他们设备上的文件。
任务
其实就是一次运行起来的一个活动栈,通常用户能当它为一个“应用程序”来启动和操作。
进程
一个应用通常会对应到一个进程,注意,不是一次运行一个进程,跟windows不一样,只要进程没有销毁,那么无论你启动几回这个任务,都可能是在一个进程里。
线程
任务、活动等的实际运行环境,每个进程包含一个或多个线程。多数情况下,Android 避免在进程里创建多余的线程,除非它创建它自己的线程,我们应保持应用程序的单线程性,也就是运行在主线程里面。
进程的生命周期
在大多数情况下,每个Android应用程序都运行在自己的Linux进程中,当应用程序的某些代码需要运行时,这个进程就被创建并一直运行下去,直到系统认为该进程不再有用为止,然后系统将回收进程占用的内存以便分配给其它的应用程序。 Android会尽可能长时间的为应用程序维持进程。
进程按重要性分类,第一类进程最重要,通常最后被销毁
1:前台(Foreground)进程,与用户当前正在做的事情密切相关。不同的应用程序组件能够通过不同的方法使它的宿主进程移到前台。当下面任何一个条件满足时,可以考虑将进程移到前台:
(1)进程正在运行一个与用户交互的Activity ,它的onResume()方法被调用
(2)进程有一个Service,该Service对应的Activity正在与用户交互
(3)进程有一个Service被调用startForeground()方法,要求到前台来执行
(4)进程有一个Service,并且在Service的某个回调函数(onCreate()、onStart()、 或onDestroy())内有正在执行的代码。
(5)进程有一正在运行的BroadcastReceiver,它的onReceive()方法正在执行
2:可见(visible)进程,它有一个可以被用户从屏幕上看到的Activity,但不在前台,它的onPause()方法被调用。这样的进程一般不允许被杀死,除非为了保证前台进程的运行不得不这样做。
3:服务(service)进程,有一个已经用startService() 方法启动的Service。虽然这些进程用户无法直接看到,但它们做的事情却是用户所关心的(例如后台MP3播放、后台数据上传下载等)。因此,系统将一直运行这些进程除非内存不足以维持所有的前台进程和可见进程。
4:后台(background)进程, 拥有一个当前用户看不到的Activity(它的onStop()方法被调用)。这些进程对用户体验没有直接的影响。如果它们正确执行了Activity生命期,系统可以在任意时刻杀死进程来回收内存,并提供给前面三种类型的进程使用。
5:空(empty)进程,不包含任何处于活动状态的应用程序组件。保留这种进程的唯一原因是,当下次应用程序的某个组件需要运行时,不需要重新创建进程,这样可以提高启动速度。
Android中的线程
概述
1:当一个Android应用被装载执行的时候,系统会创建一个线程来运行这个应用,通常称之为主线程“main thread”,也被称为UI Thread。
2:系统不会为每个组件实例去创建单独的线程,通常一个应用所有的组件都是运行在一个Linux进程和相应的主线程里面。
3:UI Thread不是线程安全的,因此你界面上的操作,都应该尽量在UI Thread里面运行,而不能在自己启动的其他工作线程里面运行。
4:因此建议,对于Android这种单线程模式:
(1)不要阻塞UI Thread
(2)不要在UI Thread之外访问Android 的UI toolkit
5:如果有长时间的处理,比如要装载一个大的图片,就需要开启一个单独的线程来装载,通常称之为Worker Thread。
一种典型的错误实现
下面在click listener里面,用一个单独的线程去下载了一个图片,并设置到ImageView上显示出来
java代码:
- public void onClick(View v) {
- new Thread(new Runnable() {
- public void run() {
- Bitmap b = loadImage ("http://sishuok.com/image.png");
- mImageView.setImageBitmap(b);
- }
- }).start();
- }
乍一看,觉得这样的实现好像是可以的,但是请注意,它违反了上面建议的第2条,“不要在UI Thread之外访问Android 的UI toolkit”,通过worker thread去设置UI Thread里面的组件,可能会导致不可预期的结果。
解决办法
要解决上面这个问题也很简单,Android提供了如下几种方式:
1:Activity.runOnUiThread(Runnable)
2:View.post(Runnable)
3:View.postDelayed(Runnable, long)
比如使用View.post来解决的示例代码如下:
java代码:
- public void onClick(View v) {
- new Thread(new Runnable() {
- public void run() {
- final Bitmap bitmap = loadImage("http://sishuok.com/image.png");
- mImageView.post(new Runnable() {
- public void run() {
- mImageView.setImageBitmap(bitmap);
- } }); }
- }).start();
- }
ADB
ADB简介
adb(Android Debug Bridge)是Android 提供的一个通用调试工具,借助这个工具,可以管理设备或手机模拟器的状态。
ADB常用操作
1:进入设备或模拟器的Shell
adb shell
通过以上命令,可以进入设备或模拟器的shell 环境中,在这个Linux Shell 中,你可以执行各种Linux 的命令,
另外如果只想执行一条shell 命令,可以采用以下方式:
adb shell [command]
2:安装应用到模拟器
adb install app.apk
3:卸载指定软件,如果加 -k 参数,为卸载软件但是保留配置和缓存文件。
adb uninstall [-k] <软件名>
4:复制一个文件或目录到设备或模拟器上:
adb push test.txt /tmp/test.txt
5:从设备或模拟器上复制一个文件或目录
adb pull /android/lib/libwebcore.os
6:取得当前运行的模拟器、设备的实例列表及每个实例的状态
adb devices
7:获取设备序列号
adb get-serialno
8:访问数据库SQLite3
Shell下直接 sqlite3