Android学习笔记

应用基础


Android是用Java语言写的,所有的Java代码和相关的资源文件被AAPT Tool 的工具打成一个Android包,后缀为apk的可运行包。这种文件专门用来发布 和安装应用程序到移动设备的。一个APK包里所有的代码都被看做一个程序。

在很多方面,每一个Android应用程序都在自己的世界(虚拟机)里单独存活。

@ 默认情况下,每个应用程序都在自己的Linux进程里。应用程序的任何一段代码需要执行时,Android都会启动这个进程,并且在处理完后要处理其他程 序时关闭这个进程。

@ 每个继承拥有自己的虚拟机,所以程序间的运行是互相隔离的。

@ 默认情况下,每个应用程序都会分到一个特殊的Linux用户ID,并被设置权限,只对应用程序自己可见和使用,当然可以对其他程序开放。

可以安排两个程序共享一个UserID,这样他们可以互相看到对方的文件,共享系统资源,并且在同一个Linux线程里运行,共享虚拟机。

组件

Android的一个核心特点就是一个应用程序可以由其他应用程序的组件来组成(在其他程序允许的前提下)。例如:如果你的程序需要显示一个图片列表并且另一个程序已经开发了合适的滚动条且允许其 他应用程序添加,那么你可以在他的滚动条基础上开发,比自己开发要方便的多。你的程序不需要合并他的代码只需要一个连接即可。相比之下,当我需要某个程序然后可以启动他时是最简单的。

想要这样的话,当这个程序的某一部分被别人用到时,系统必须能都启动一个应用程序的进程,并且实例化这些相关的Java对象,因此,与其他系统的应用程序不同的是,android程序没有一个单一的入 口程序(例如main方法),相比下,android有四种基本的组件可以被系统实例化、启动。包括:Activities、Services、Broadcast receivers、Content providers。

Activities

Activity提供了一个可视化的用户接口,例如:一个activity可以提供一个列表,供用户选择,或者在拍照时显示照片列表。一个短信程序有一个activity显示联系人列表,另一个activity可以写短信 并且选择联系人发送,别的activity可以查看以往的短信内容并且更改设置。这几个activity共同在一个用户界面里发挥着作用,他们之间互相依赖,每一个都是继承自activity基类的子类。

一个应用程序可以包含一个或者多个activity,例如刚提到的短信程序。需要多少就可以包含多少。通常,被标记的第一个activity先运行。当前activity启动完就启动下一个。

每个activity都被分配一个默认的窗口来绘制。通常是全屏,也可以比屏幕小或者浮在其他窗口上。一个activity可以利用额外的窗口,例如:在activity里探出一个让用户想用的弹出对话框,或者显 示一个给用户显示重要信息供用户选择的特殊选项。

窗口的可视内容由层式结构的view来显示,他们继承自view基类。每个view控制一个特定矩形空间内的窗口。父view控制并且组织子view 的显示。子view(上层的view)被绘制在矩形里响应用户的操作 ,从而与用户交互信息。例如可以显示一副小图片用户点击图片时响应一个事件。Android有很多现成的view可以被直接使用,包括按钮、文本框、滚动条、菜单、选择框等等。

通过Activity.setContentView()可以改变activity的显示,content view 是根视图。

Services

servers没有用户接界面,但它确实在后台不定期的运行着。例如:但用户处理其他事情时,一个service可以背景播放音乐、后台读取网络数据或者处理其他的东西并且提供给其他的activity,每一个 servers都继承自Service基类。

一个最好的例子就是媒体播放器。播放器大概需要一个或多个activity来允许用户选择歌曲并且播放。然而,播放器不必是当前活动的activity,用户可能在播放时做别的事情。播放器需要启动一个后 台来持续播放音乐。系统会一直播放这个播放器service甚至当他隐藏时。

我们可以链接到一个正在运行的service,或者启动一个没有运行的service,当连接成功时,你可以和service的进程通信。例如播放器,用户可以控制他的开始暂停重放等功能。

像activity和其他的组件一样,services运行在主线程。所以不会阻塞其他的组件或者用户界面。他们往往启动一个新的任务。可参考Process and Threads。

Broadcast receivers

Broadcast receiver这个组件只是接受receive和响应Broadcast广播通知。很多广播来源系统代码,例如通知时区已经改变、低电量、图片被选中、用户改变了语言设置,此时用户开始广播,例如,同 事其他应用程序网络数据已经下载ok可以使用了。

一个Broadcast receiver 可以响应多个广播,所有的Broadcast receiver 都继承自BroadcastReceiver基类。

Broadcast receivers 没有用户界面。然而,他们可以在接收到消息启动一个activity,或者使用notificationManager 去提醒用户。Notifications 可以通过一些方式引起用户注意:闪动背光、 震动、发声等等。通常在状态栏显示一个提示图标让用户可以查看。

Content providers 内容提供

Content providers为其他程序提供数据集,这些数据可以保存在系统文件里或者sqlite数据库里,Content providers继承自 ContentProvider基类,实现了一些标准的方法,可以 让程序检索或者改写其中的数据。程序不能直接的调用那些方法。ContentResolver不能与content provider直接通信,但可以用过ContentResolver 。ContentResolver 可以跟任何的content provider通信,可以与provider合作管理通 信进程。


激活组件: intents 

当ContentResolver接收到一个请求时,运行content providers。其他三个组件activities、services、broadcast receiver被Intents异步消息启动。intent 里包含着所传递消息的内容。对于activity和services来说,intents定义了请求,指定了目标数据的URI。例如,可以让发送一个请求让用户接受或者输入一些文本信息。对broadcast receivers来说,intents定义了一些声明的事件(?),例如:他可以在相机的按钮被按下时发出一些信息。 

下面是每个组件类型对应的启动方法 

@ Context.startActivity() 和 Activity.startActivityForResult()方法可以启动一个activity。activity可以查看启动他的的intent,通过调用getIntent()方法。Android 系统调用onNewIntent()方法来传递intents。

一个activity经常会启动下一个activity。使用startActivityForResult() 代替 startActivity()方法可以从启动的activity获取一些返回值。例如,可以启动一个activity让用户选择一张图片并且返回所选的图片,被传递回来的数据被送到前一个activity的onActivityResult()方法里。 

@ Context.startService()可已启动一个service。Android通过service的onStart()方法传递intent对象。类似的,intent可以传递至Context.bindService()来获取一个正在运行的服务的句柄,这个服务可通过onBind()方法获取这个intent对象(如果服务没有启动可以通过bindService()来启动服务)。例如,一个activity可以很容易的建立一个到音乐播放服务的连接,他可以提供给用户一些控制音乐播放器的借口。activity可以调用bindService()方法来建立连接,然后调用已经定义的关于播放器的一些方法。 

@ 应用程序可以通过传递intent来初始化一个Broadcast广播,相关方法:Context.sendBroadcast()、Context.sendOrderedBroadcast()、Context.sendStickyBroadcast() ,activity会调用所有对此广播关注的广播接收器,然后将intent传递给这些方法。

 

关闭组件 

只有当内容提供商从内容接收者接收到一个请求时才会启动。广播消息接受者只有当接收到一个广播消息时才会被启动。所以他们不需要手动关闭。 

activity则另当别论,他们提供给用户界面,他们和用户保持长时间的对话并且保持活跃状态,甚至空闲时,仍然保持联系。类似的,services也会运行很长一段时间。所以Android提供关闭activity和services的一些方法: 

@ finish()会关闭activity,那些被startActivityForResult()方法启动的activity可以通过finishActivity()关闭。

@ services 可以通过 stopSelf()和Context.stopService() 方法被关闭。 

当系统不再使用他们或者没有足够内存世,系统可以自行关闭一些组件,后面的章节会讨论这种情况。 

 

manifest 文件 

在Android启动一个应用组件前,必须让activity知道那些组件的存在,所以应用程序必须在manifest文件里声明自己的组件,并把manifest文件打包到Android 包里。apk文件包含程序的代码、文件和资源。 

manifest是一个结构化的xml文件,被命名为AndroidManifest.xml,里面声明了应用的组件、一些除了系统默认库的用户自定义库和程序希望被授予的一些权限。 

manifest主要任务是告诉Android应用程序的部件。例如,activity可以这样声明:

[xhtml]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest . . . >  
  3.     <application . . . >  
  4.         <activity android:name="com.example.project.FreneticActivity"  
  5.                   android:icon="@drawable/small_pic.png"  
  6.                   android:label="@string/freneticLabel"   
  7.                   . . .  >  
  8.         </activity>  
  9.         . . .  
  10.     </application>  
  11. </manifest>  
 <activity>元素的name属性是继承自activity基类的activity的类名,icon和label指向了资源文件。 

别的组件声明起来就简单的多了。服务的标签:<service>,广播接受者标签:<receiver>,内容提供商标签:<provider>。

activity、services和内容提供商不在这里声明的话是不能运行的。然而,广播可以在manifest文件里声明或者动态的在代码里创建并且调用Context.registerReceiver()方法声明。

 

Intent filters 

Intent对象可以显示的定义一个目标组件。那样的话,Android就可以找到并运行它。如果目标组件没有明确的定义出来,Android会相应最佳的组件来响应这个Intent,这是通过比较<intent filters>标签里的所有组件来选择相应的。intent filters (Intent过滤器)告诉Android所有可以处理的组件。和组件的其他重要信息一样,在manifest文件里声明。

下面是一个添加了两个Intents过滤器的例子:

[xhtml]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest . . . >  
  3.     <application . . . >  
  4.         <activity android:name="com.example.project.FreneticActivity"  
  5.                   android:icon="@drawable/small_pic.png"  
  6.                   android:label="@string/freneticLabel"   
  7.                   . . .  >  
  8.             <intent-filter . . . >  
  9.                 <action android:name="android.intent.action.MAIN" />  
  10.                 <category android:name="android.intent.category.LAUNCHER" />  
  11.             </intent-filter>  
  12.             <intent-filter . . . >  
  13.                 <action android:name="com.example.project.BOUNCE" />  
  14.                 <data android:mimeType="image/jpeg" />  
  15.                 <category android:name="android.intent.category.DEFAULT" />  
  16.             </intent-filter>  
  17.         </activity>  
  18.         . . .  
  19.     </application>  
  20. </manifest>  
 例子中的第一个过滤器是常见的。他标记了所有显示给用户让用户选择启动的组件。换句话说就是程序的入口点。是用户启动程序后最先见到的。

 第二个过滤器声明了可以提供(perform)的特殊数据类型。(对否?呵呵)

 一个组件可以有数个Intent过滤器。每一个都可以声明不同的权限。如果没有添加过滤器的话,只能使用Intent方式被启动。 

对于广播接受者来说,如果在代码中被创建和注册,那么直接被实例化成一个IntentFilters对象,其他的过滤器都在manifest文件里被声明。


前面说的那样,一个activity可以启动另一个activity,包括在其他应用程序里定义的activity。假如你想让用户显示一些街道地图的位置信息,已经存在这样一个功能的activity了,所以你只需填好Intent信息并曾通过startActivity()方法传递Intent,地图浏览器就会显示地图。当用户按Back键时,会回到原来的activity。

 

对于用户来说,地图浏览器就像你程序的一部分一样,尽管他是在另一个应用程序里定义并且运行的。Android为了良好的用户体验而让两个activity运行在同一个任务里。简单地说,用户认为一个task就是一个“应用程序”。应用程序是一组相关的activity,排列在一个堆栈里。通常,堆栈里的主activity会启动用户在启动器里选择的那个activity。栈顶的activity就是当前运行的,得到用户焦点的。当启动了另一个activity,新的activity就会到栈顶,然后运行。上一个activity仍然留在栈里。当用户按返回Back键时,当前activity就被弹出栈,上一个activity继续运行。

 

栈里保存着很多的对象。如果一个任务有很多个activity实例例如多个地图浏览器,那么栈会给每一个实例分配单独的入口,栈里的activity实例永远不会重新分配,只会被弹进,弹出。

 

一个任务是整个activity的堆栈,不是指一个类或者manifest文件中的一个元素。所以不可能单独的设置栈里的某一个activity的值。事实上软任务的值是主activity里设定的。例如我们以后会提到的“任务的affinity”。从affinity读取的值可以设置到任务的主activity中。

任务里的所有activity被看做一个整体。这个整理可以被拿到前台或者放在后台运行。假设当前任务有四个activity,一个正在运行的和三个在堆栈里的。用户按下HOME键后,会转到程序触发器里,让用户选择一个新的应用程序,实际上是一个新的任务。当前任务转到后台,新任务的主activity开始运行。一段时间后,用户又回到HOME屏,选择启动刚才隐藏的任务,隐藏的任务中四个activity都会转到前台。当用户按下BACK键时,屏幕不会显示刚刚离开的activity(上一个任务的主activity),而会显示当前任务中,当前栈中的上一个activity。(即不会在任务间返回只会在activity间返回)。

 

上面只是说了activity和任务的默认的行为,其实可以修改他们几乎所有的方面。activity和任务的联系、任务里activity的行为、被Intent对象的标记和<activity>里的元素影响着,无论是请求者还是回应者都有一定决定权。

 

主要的Intent标记如下:

FLAG_ACTIVITY_NEW_TASK 

FLAG_ACTIVITY_CLEAR_TOP 

FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 

FLAG_ACTIVITY_SINGLE_TOP

 

主要的 <activity>属性如下:

taskAffinity 

launchMode 

allowTaskReparenting 

clearTaskOnLaunch 

alwaysRetainTaskState 

finishOnTaskLaunch

 

接下来会介绍这些标记和属性的作用,之间的相互影响,和使用它们时的应考虑到的问题。

 

 

Affinities (亲和力) and new tasks Affinities 和新任务

(这段很别扭)

默认情况下,一个程序里的各个activity之间都有联系,他们共属于同一个任务task,然而,通过taskAffinity的<activity>属性可以给每个activity设置个人的Affinities 。定义在不同应用程序里的activity可以共享自己的affinities,定义在相同的程序里的activity也可以声明不同的affinities。affinity 在两种情况下起作用:一种是当通过Intent启动的activity中包含有FLAG_ACTIVITY_NEW_TASK标记时,一种是activity的allowTaskReparenting属性被设置成true时。

 

FLAG_ACTIVITY_NEW_TASK 标记

如前所述,被其他activity通过startActivity()方法启动的activity,他和启动他的前一个activity属于同一个堆栈。然而,如果传递的Intent对象中被设置了FLAG_ACTIVITY_NEW_TASK标记,那么系统会在一个新的任务里启动activity。通常,启动的activity就像名字那样,是一个新的任务。然后,那不是必须的,如果已经存在了和要启动的activity相同affinity的任务,已存在的任务会被运行,如果不存在才会重启一个新的任务。

 

allowTaskReparenting 属性

如果一个activity的allowTaskReparenting 属性为true,那么这个activity可以从启动他的任务里移动到和他拥有相同affinity的activity所在的任务里,如果那个后者的activity正在顶端运行时。例如,假设一个activity(一个旅行程序的一部分)报告所选城市的天气情况。这个天气activity和程序里的其他的activity拥有相同的affinity,并且天气activity的allowTaskReparenting属性为true,如果程序的其中一个activity启动天气报告的话,那么这个天气报告部分将会属于你的activity。然而,当旅行的程序接下去运行时,报告天气这部分会在另一个任务里显示和运行。

 

如果一个apk包含多个应用程序,你最好分配不同的affinity给activity。

 

 

Launch modes

 

// TODO

Launch modes 运行模式

Clearing the stack 清理堆栈

Starting tasks 启动任务


当应用程序组件第一次运行时,Android在一个单独运行的线程里启动一个linux进程,默认情况下,这个程序的所有组件都将运行在这个进程和线程里。

然而,你可以让组件运行在其他的进程里,你也可以为任何进程新建额外的线程来运行。

 

 

进程

 

组件运行的进程受到manifest配置文件所控制。activity、service、receiver、provider这四种组件都有一个进程属性可以指定。这些属性可以让组件运行在自己的进程里,或者与其他组件共享进程或者禁止共享。可以让不同应用程序的组件运行在相同的进程里,只要提供相同的linux用户ID和相同的签名。application元素也有一个process属性,用来为所有的组件提供一个默认值。

 

所有的组件都在指定进程的主线程里被实例化,系统通过主线程调度组件。不会为每个实例都创建独立的线程。所以,这些回调方法:例如View.onKeyDown()方法报告用户的行为、声明周期的通知(在下面的章节讨论)总是运行在进程的主线程里。这就意味着系统调用组件时,没有组件可以一直运行很长时间或者以阻塞的方式操作(比如网络操作或者循环计算),因为这将会阻止进程里其他任何的组件加入。你可以为一些需要很长时间的操作建立独立的线程,作为接下来要讨论的主题:下一个。

 

Android在某些时刻可以据顶是否关闭一个进程,当可用内存很低时,并且其他进程需要立刻服务于用户时。在进程里运行的应用程序组件将被销毁。当这些组件重新被用户使用时,进程会重新启动。

 

当系统决定哪个进程将要被终止时,Android比较一下他们对用户的重要性。例如:一个不可见的activity比一个可见的activity更容易被关闭。是否关闭一个应用程序,取决于进程里运行中的activity的状态。这些状态待会在生命周期那一节中再讨论。


线程

 

即使你限制你的应用程序在一个独立的进程里,有时你需要新建一个线程来运行后台程序。因为界面必须对用户的操作很快的做出响应,所以像网络下载这样的一些耗时的行为,不该在主线程里进行。任何不能及时得到响应的事情都应该放在独立的线程里进行。

 

我们可以使用Java中的Thread对象来建立线程。Android提供一些基本的使用方便的类来管理线程。Looper可以在线程里运行一个消息循环,Handler来处理消息,Handler Thread 用来建立一个消息循环的线程。

 

远程程序调用

 

Android有一个轻量级的远程程序调用机制RPCs,当一个方法在本地声明,在其他地方被调用(比如其他进程里被调用),而不用返回值。这就需要将方法和数据分解为底层的操作系统可以理解的粒度,从本地的进程和地址空间传输到远程的进程和地址空间里,并在那里组装好。返回值需要回传。Android提供这些工作的代码,所以你可以专注于定义和实现这些接口。

 

一个RPC接口可以只包含方法。默认情况下,所有的方法都会同步化,本地方法会一直阻塞知道远程方法完成,甚至没有返回值。

 

简短来说,他的工作机制是这样的:先声明RPC的接口,用一个简单的IDL即接口定义语言来实现。通过声明,AIDL工具会生成java接口的定义,可以被本地和远程进程都能使用的定义。他包含两个内部类,如下图:

 

 

这两个内部类包含了所有你需要管理员远程调用你用IDL声明的接口的代码(太拗口)。两个内部类都要事先IBinder接口。其中一个在本地使用,你写代码时可以不用处理它,另一个是Stub,继承自Binder类。为了使IPC调用的内部代码有效,它包含了你用RPC接口所声明的方法。你可以继承Stub类来实现哪些方法,像上图那样。

 

通常情况下,远程进程会被一个后台服务所管理,因为服务可以通知系统给进程发消息,并且能连接到其他进程。他不仅包含了由aidl工具生成的接口文件,并且浩瀚Stub的子类事先RPC的方法。一个服务的客户端可以可以只有aidl工具生成的借口文件。

 

下面是一个服务和他的客户端是如何建立链接:

 

@服务的客户端(在本地端)实现onServiceConnected()和onServiceDisconnected()方法,所以当一个既定的远程服务链接成功时、或者断开连接时,他们可以被通知到。调用bindService()方法可以建立连接。

@服务的onBind()方法可以实现接受或者拒绝方法。这取决于接收到的Intent(bindService())。如果连接被接受了,他会返回一个Stub子类的实例。

@如果服务接受了连接,Android会调用客户端的onServiceConnected()方法,并且传递给一个IBinder对象,IBinder对象是一个被服务管理的Stub子类的代理。通过代理,客户端可以调用远程方法。

 

上面简短的介绍省略了一些RPC机制的细节。更多信息可以查看Designing a Remote Interface Using AIDL或者IBinder类的介绍。

 

 

线程安全方法

 

在一个新的contexts里,你实现的方法可能被多于1个的线程调用,因此必须是线程安全的。

这就是为什么被叫做远程方法,就像上节讨论的RPC机制一样。当调用一个实现自IBinder对象的方法时,此方法和IBinder一样属于同一个进程,那么方法在调用者的线程里执行。然而,如果在其他的进程里调用方法,那么方法在一个从Android维护的和IBinder在同意进程里的线程池里选择的一个线程里运行(太直译了),而不在主线程里运行。例如,一个服务的onBind()方法可以被服务所在进程的主线程所调用。onBind()返回的对象所实现的方法(例如一个实现RPC方法的Stub的子类)可以被线程池的方法调用。因为服务可以有多个客户端,同时可以有多个线程池服务于相同的IBinder方法。IBinder方法必须是方法安全的。

 

同样的,一个content provider内容提供商可以接收来自其他进程的数据请求。尽管ContentResolver和ContentProvider隐藏了管理进程间通信的细节,ContentProvider 方法响应这些需求:query方法,insert方法,delete方法,update更新方法,和getType方法,从contentProvider进程的线程池的线程里被调用,而不是主线程里。他们也可能同时被多个线程调用,所以也必须是线程安全的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值