啥是Intent

在一个Android程序中,主要由3种组件(Activity,BroadCast,Service)组成的,这三种组件是独立的,但是它们之间可以相互调用,协调工作。这些组件之间的通信主要是由Intent来完成的。Intent在其中起着一个媒体中介的作用,专门提供组件相互调用的相关信息,实现调用者与被调用者之间的解耦。
Intent负责对应用中一次操作的动作,动作涉及的数据以及附加数据进行描述,Android则根据此Intent的描述负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。

Intent对象的组成

  • 组件名字 Component:定位合适的目标组件
  • 动作 Action:用来标示要广播的事件,动作很大程度上决定了数据和附加信息,就像一个方法名决定了参数和返回值一样,因此应尽可能明确指定动作,同时必须确保动作字符串的唯一性,一般使用java包名。
  • 数据 data :数据是作用Intent上的数据的URI和数据的MIME类型,不同的数据具有不同的规范,当匹配一个Intent到一个能够处理数据的组件的时候,明确其数据类型(MIME)和URI很重要。
  • 种类 Catoragry :用于作为被执行动作的附加信息
  • 附加信息 Extra :传输数据的重要方法
  • 标志 Flag :主要用来指示Android程序如何去启动一个活动,例如,活动应该属于哪个任务。和启动后如何对待它,例如它是否属于最近的活动列表。
组件名称

组件名称是可选的,如果设置了,Intent对象传递到指定的类的实例,如果没有设置,Android使用Intent中的其他信息来定位合适的目标组件。组件名字的设置可用如下方法。

动作 标准的Activity动作


在上述两个表中,需要将其转换为对应的字符串信息,比如将ACTION_TIME_TICK转换为android.intent.action.TIME_TICK
除了预定义的动作,我们还可以自定义动作字符串,来启动应用程序中的组件,这些新发明的字符串应该包含一个应用程序包作为前缀,如com.exemple.SHOW_COLOR

数据

种类


附加信息

标志


Intent 作用

Intent用处可归纳如下几点:

  1. 负责Android设备上安装的任意应用程序组件之间的交互,不管它们是哪个应用程序的一部分都是如此,这就把设备从一个包含相互独立的组件集合的平台变成一个互联的系统。
  2. 通过定义隐式或者显式的Intent来启动Activity或者Service。
  3. 用于在系统范围内广播消息,应用程序可以通过注册一个Broadcast Receiver来监听和响应这些广播的Intent,这样就可以基于内部的,系统的或者第三方应用程序的事件创建事件驱动的应用程序。
  4. Android通过广播Intent来公布系统事件,比如网络状态,电池电量的改变。本地Android应用简单得注册监听特定的广播Intent并作出相应的响应的组件,这样就可以通过监听相同的Intent 的BroadCast Receiver来替换本地提供的应用程序。
使用Intent 启动 Activity
  • 显式启动Activity (条件:目标Activity必须在manifest文件中注册):
    Intent intent = new  Intent(MainActivity.this,Detail.class);
    startActivity(intent);

上述代码执行后,新的Acitivity将会被创建,启动和运行,并移动到Activity堆栈的顶部。

  • 隐式启动Activity
    隐式启动可以要求系统启动一个可执行给定动作的Activity,而不必知道需要启动哪个应用程序或者Acitivity,这种情况下运行时会自动解析Intent来选择最合适的Activity,如果有多个Acitivity都能够执行指定动作,那么系统将会向用户呈现选项,让用户来选择。

方法一:指定ACTION以及执行当前ACTION所需数据的URI

Intent intent = new Intent(Intent.ACTION_DIAL,Uri.parse(“tel:555-2368”));

方法二:使用自定义的Activity来响应当前的隐式Intent

Intent intent = new Intent("com.androidDemo.intent.action.ShowView");

在manifest中添加意图过滤器intent-filer

<activity android:name=".BasicViewActivity" android:label="Basic View Tests">
<intent-filter>
    <action android:name=" com.androidDemo.intent.action.ShowView "/>
        <category android:name=“android.intent.category.DEFAULT”/>(一定要加)
</intent-filter>
</activity>

  • 关闭Activty
    finish()

如果当前的Activity不是主活动,那么执行finish方法后,返回到调用它的那个Activity,否则将返回到主屏幕中。
关闭Activity还可以使用finishActivity方法实现该方法用来关闭使用startActivityForResult方法启动的Activity。

使用Intent在Activity之间交换数据

Extra作为一个Bundle对象存储在Intent中,用于向Intent附加基本类型值,可以使用putExtra可以添加附加的数据,在目标Activity中使用getExtras方法来取出数据。
下面是使用Intent在Acitivity之间传输数据的代码片段:
发送方:

Intent intent = new Intent(MainActivity.this,RegisterActivity.class);
Bundle bundle = new Bundle();
bundle.putCharSequence("user",user);
bundle.putSerializable("info",info);
intent.putExtras(bundle);
startActivity(intent);

接收方:

Intent intent = getIntent();
Bundle bundle = intent.getExtas();
bundle.getString("usr");
bundle.getSerializable("info");

调用另一个Activity并返回结果

通过startAcitivity启动Acitivity,在关闭的时候不会返回任何数据。因此如果需要在调用Acitivity后返回数据则需要使用startActivityForResult来实现,代码块如下:

发送方:

startActivityForResult(intent,request_code);
protected void onAcitvityResult(int requestCode,int resultCode,Intent data){
if(requestCode==xxxx&&resultCode=yyyyy){
data.getExtas();
...............................
}

}

接收方:

setResilt(0x717,intent);
finish();

确定Intent能否解析

在使用Intent启动一个Acitivity之前,必须先确定用户设备上安装了能够处理当前请求的应用程序。这个可以通过调用Intent的resolveActivity方法,并向该方法传入Package Manager 进行查询。具体的代码块如下:

PackageManager pm = getPackageManager();
ComponentName cn = intent.resolveAcitivity(pm);
if(cn == null){
//表示没有找到对应的应用程序
//可以选择禁止相关的功能,或者到应用市场下载对应的应用程序
}else{
//表示找到对应的应用程序
startActivity(intent);
}
使用Intent 启动和绑定 Service
  • 启动服务

    intent = new Intent(this, EchoService.class);
    startService(intent)
  • 停止服务

    intent = new Intent(this, EchoService.class);
    stopService(intent);
  • 绑定服务

    intent = new Intent(this, EchoServices.class);
    bindService(intent, conn, Context.BIND_AUTO_CREATE);
  • 解除绑定服务

    unbindService(conn);
使用Intent 发送BroadCast Intent
Intent intent = new Intent(Constant.ACTION_NAME);
sendBroadcast(intent);//普通广播
sendOrderedBroadcast(intent, null);//有序广播
sendStickyBroadcast(intent);//粘性广播

意图过滤器 IntentFilter

当在android程序中使用显示意图的时候,意图对象中只用组件名字内容就可以决定那个组件应该获得这个意图,而使用隐式意图的时候android程序必须查找一个最适合的组件去处理意图,该过程是通过比较意图对象内容和意图过滤器来完成的。
如果一个组件没有任何Intent过滤器,它仅仅能接收显示的意图,而声明了意图过滤器的组件可以接收显示和隐式意图。
当一个意图对象的动作,数据(URI和数据类型),种类,这三者都符合一个意图过滤器的时候,才能考虑是否接收意图,而附加信息和标识在解析哪个组件接收意图时不起作用。

意图过滤器的动作匹配

动作测试:

<intent-filter>
<action android:name="com.example.project.SHOW_CURRENT"/>
<action android:name="com.example.project.SHOW_RECENT"/>
<action android:name="com.example.project.SHOW_PEDDING"/>
......
</intent-filter>

尽管意图对象仅定义一个动作,在意图过滤器中可以列出多个,列表不能为空,即过滤器中必须至少包含一个标签,否则会阻塞所有Intent
规则:
如果Intent Filter 包含了指定的动作,那么就认为动作匹配了,如果检测到没有任何一个动作和Intent指定的动作相匹配,就认为动作匹配失败了。

意图过滤器的种类匹配

种类测试:

<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
......
</intent-filter>

在接收隐式意图的Activity必须在过滤器中包含android.catagory.DEFAULT.
Intent Filter 必须包含待解析Intent的所有Category,但是可以包含Intent不包含的其他category。一个没有指定category的Intent Filter只能和没有任何category的Intent相匹配。

意图过滤器的数据匹配

数据测试:

<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http"/>
<data android:mimeType="audio/mpeg" android:scheme="http"/>
......
</intent-filter>

每个标签可以指定URI和数据类型,URI可以分成scheme,host,port,path这几个独立的部分,格式如下

scheme://host:port/path

例如:content://com.example.project:200/folder/subfolder/etc
其中scheme是content,host是com.example.project,port是200,path是/folder/subfolder/etc,host和port组成了URI授权,如果host没有指定则忽略port。这些属性都是可选的但是相互之间并非完全独立的,如果授权有效,则sheme必须指定,如果path有效,则sheme和授权必须指定。当意图对象中的URI与过滤器中的URI规范相比较的时候,它仅与过滤器中实际提到的URI部分相比较,例如过滤器仅指定了sheme那么所有具有该sheme的URI都能匹配该过滤器,如果过滤器指定了scheme和授权没有指定path那么,不管path如何,具有该sheme授权的URI都能匹配,如果过滤器中指定了sheme,授权和path则仅有具有相同sheme,授权和path的URI能够匹配。然而过滤器中的path中可以包含通配符来允许部分匹配。
(也就是只要Intent的数据是Intent Filter数据集合中的一项就可以通过匹配)mimeType:Intent对象和过滤器都能使用来包含子类型,例如text/或者audio/

BroadCast Receiver是啥

Broadcast Receiver是用于接收广播通知的组件。广播是一种同时通知多个对象的事件通知机制。
Android中的广播来源于系统事件,例如按下拍照键,电池电量低,安装新应用等。还有普通应用程序。如启动特定线程,文件下载完毕等。
广播接收器通常初始化独立的组件或者在onReceiver方法中发送通知给用户,如果广播接收器需要完成更加耗时的任务,应该启动一个服务而不是一个线程,因为不活跃的广播接收器可能被系统停止。

BroadCast Receiver分类

  • 普通广播
    sendBroadcast()这个方法的广播能够按照注册的先后顺序发送给所有广播接收者,如果设置了广播接收者的优先级,优先级如果恰好与注册顺序相同,则不会有任何问题,如果顺序不一样,会出leaked IntentReceiver 这样的异常,并且在前面的广播接收者不能调用abortBroadcast()方法将其终止,如果调用会出BroadcastReceiver trying to return result during a non-ordered broadcast的异常,先接收到广播的receiver可以修改广播数据。
  • 有序广播
    sendOrderedBroadcast()方法priority的属性能起作用,并且在队列前面的receiver可以随时终止广播的发送。还有这个api能指定final的receiver,这个receiver是最后一个接收广播时间的receiver,并且一定会接收到广播事件,是不能被前面的receiver拦截的。这个特性可以用来统计系统中能监听某种广播的Receiver的数目。
  • 粘性的广播
    sendStickyBroadcast()字面意思是发送粘性的广播,使用这个api需要权限android.Manifest.permission.BROADCAST_STICKY,粘性广播的特点是Intent会一直保留到广播事件结束,而这种广播也没有所谓的10秒限制,10秒限制是指普通的广播如果onReceive方法执行时间太长,超过10秒的时候系统会将这个广播置为可以被销毁的候选,一旦系统资源不够的时候,就会撤销这个广播。

使用BroadCast Receiver 监听广播

要让BroadCast Receiver 监听广播需要完成如下操作:

  1. 在代码中进行动态注册广播,或者在Manifest 清单文件中静态注册
    二者的对比如下:
  2. 使用意图过滤器Intent Filter 来指定它要监听的BroadCast Intent
  3. 创建新的BroadCast Receiver 重写onReceive方法,在接收到一个通过Intent Filter 过滤后的BroadCast Intent,就会执行onReceive方法,在onReceive方法中可以更新内容,启动Service,更新UI或者使用Notification Manager来通知用户。BroadCast 也运行在主线程中,因此主要的处理工作不能在BroadCast Receiver中直接完成
动态注册广播
  • 动态注册:

    IntentFilter intentFilter = new IntentFilter(Constant.ACTION_NAME);
    broadCast = new EchoBroadCast();
    registerReceiver(broadCast, intentFilter);
  • 取消注册:

    unregisterReceiver(broadCast);
  • 发送广播:

    Intent intent = new Intent(Constant.ACTION_NAME);
    Bundle b = new Bundle();
    b.putString("Message", "This is The Message From MainActivity!");

    intent.putExtras(b);
    sendBroadcast(intent);

  • 服务类的编写:

    public class EchoBroadCast extends BroadcastReceiver {

    public void onReceive(Context context, Intent intent) {
    String ACTION = intent.getAction();
    if(ACTION.equals(Constant.ACTION_NAME)){
    Log.i(Constant.TAG_NAME, intent.getExtras().getString("Message"));
    }
    }
    }
静态注册BroadCast Receiver
<receiver android:name="com.example.testbroadcastreceiverregiststatic.EchoBroadCast" >
<intent-filter>
<action android:name="com.example.testbroadcastreceiverregiststatic.ACTION" />
</intent-filter>
</receiver>

OrderedBroadCast

当发送有序广播的时候,Intent将会按照优先级顺序被传递给所有具有合适权限的已经注册的接收器,优先级越大越先接收到。

启动广播代码:

sendOrderedBroadcast(intent, null);

广播中的重要方法:

setResultExtras(b); // 优先接收到Broadcast的接收者可以通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,然后传给下一个接收者

Bundle b = getResultExtras(true);
int count = b.getInt("Number");// 将数据取出
abortBroadcast();// 终止广播

清单文件中注册:

<receiver  android:name="com.example.testbroadcastordered.FirstReceiver">
<intent-filter android:priority="4" >
<action android:name="com.example.testbroadcastordered.ACTION" />
</intent-filter>
</receiver>

第一个广播代码:

public void onReceive(Context context, Intent intent) {
Log.i(Constant.Tag_NAME, "FirstReceiver");
Bundle b = intent.getExtras();
int count = b.getInt("Number");// 将数据取出
count++; // 修改数据
b.putInt("Number", count); // 将数据存入
setResultExtras(b); // 优先接收到Broadcast的接收者可以通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,然后传给下一个接收者
}

其他广播代码:

public void onReceive(Context context, Intent intent) {
Log.i(Constant.Tag_NAME, "SecondReceiver");
Bundle b = getResultExtras(true);
int count = b.getInt("Number");// 将数据取出
count++; // 修改数据
b.putInt("Number", count); // 将数据存入
setResultExtras(b); // 优先接收到Broadcast的接收者可以通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,然后传给下一个接收者
}

最后一个广播代码:

public void onReceive(Context context, Intent intent) {
Log.i(Constant.Tag_NAME, "LastReceiver");
Bundle b = getResultExtras(true);
int count = b.getInt("Number");// 将数据取出
count++; // 修改数据
Log.i(Constant.Tag_NAME, "一共经过了:" + count + " 个广播");
abortBroadcast();// 终止广播
}

StickyBroadCast

Sticky Intent 可以保存它们最后一次广播的值,并且当有一个新的接收者,被注册为接收该广播的时候,它们会把这些值作为Intent返回,当调用regesterReceiver来指定一个匹配的Sticky BroadCast Intent 的intentFilter 时,返回值将是最后一次Intent广播。
要广播自己的Sticky,必须拥有BROADCAST_STICKY用户权限,不是必须指定一个接收器来获取Sticky Intent的当前值。

注册:

IntentFilter filter = new IntentFilter("com.example.testbroadcaststicky.ACTION");
registerReceiver(new EchoBroadCast(), filter);

发送:

intent = new Intent("com.example.testbroadcaststicky.ACTION");
intent.putExtra("Number",88888888);
sendStickyBroadcast(intent);

移除:

removeStickyBroadcast(intent);

设置权限:

android.permission.BROADCAST_STICKY

public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSendStickyBroadCast:
intent = new Intent("com.example.testbroadcaststicky.ACTION");
for(int i=0;i<10;i++){
intent.putExtra("Number",i);
sendStickyBroadcast(intent);
}
break;
case R.id.btnSendAnotherStickyBroadCast:
intent = new Intent("com.example.testbroadcaststicky.ACTION");
intent.putExtra("Number",88888888);
sendStickyBroadcast(intent);
break;
case R.id.btnRemoveStickyBroadCast:
if(isregistered){
removeStickyBroadcast(intent);
isregistered = false;
}
break;
case R.id.btnRegisterStickyBroadCast:
IntentFilter filter = new IntentFilter("com.example.testbroadcaststicky.ACTION");
registerReceiver(new EchoBroadCast(), filter);
isregistered = true;
Log.i("EchoBroadCast", "注册成功");
break;
default:
break;
}
}



从上面截图可以看出在发送10次Intent后最后一个intent数值为9,下一次注册StickyBroadCast的时候接收到的是上一次的最后一个Intent,数值为9,如果再发送一次值为88888888的Intent,这时候最后一个Intent的值为88888888,如果再次注册,返回的是88888888

广播接受者实现过程需要注意的点

  1. 广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁。
  2. 广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框。
  3. 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉。
  4. 耗时的较长的工作最好放在服务中完成。

Service 特点

  • 适用于耗时操作,并且不需要与用户交互的功能。
  • 其他应用程序组件能够启动服务并且即使用户切换到另一个应用,服务还可以在后台运行。
  • 组件能够绑定到服务并与之交互,甚至执行进程间通信。
  • Service 比处于非活动状态的Activity更高的优先级,因此当系统请求资源时,它们被终止的可能性很小。如果运行时过早得终止了一个已经启动的Service,只要有足够的资源可用,则运行时就会重新启动它。
  • 一个Service的优先级可以提升到和前台Activity的优先级一样高。
  • 开发人员可以将服务设为私有从而阻止其他应用程序访问。
  • 虽然Service在运行的时候没有专门的GUI,但是它们和Acitivity以及BroadCast Receiver一样还是运行在应用程序进程的主线程中,因此必须通过Thread和AsyncTask类把耗时的进程移到后台进程中。通过使用独立的线程,开发人员能减少应用不相应错误的风险,并且应用程序主线程仍然用于用户与Activity交互。

Service 的类型

  • Started Service:当应用程序组件通过调用startService()方法启动服务时候,服务处于started状态。一旦启动,服务能够在后台无期限运行,即使启动它的组件已经被销毁。通常,Started Service执行单个操作并且不会向调用者返回结果。如果操作完成,服务需要停止自身.
  • Bound Service :当应用程序通过bindService()方法绑定到服务的时候,服务处于bound状态,绑定服务提供客户端-服务器接口,以允许组件与服务交互,发送请求,获得结果,甚至使用进程间通信跨进程完成这些操作。仅当其他应用程序组件与之绑定的时候,绑定服务才能运行。多个组件可以绑定到一个服务上。只有在他们都解除绑定的时候服务被销毁。
    服务也可以同时属于上述两种类型既可以启动,无限期运行,可能绑定。这取决于是否实现一些回调方法:onStartCommand()方法允许组件启动,onBind方法允许组件绑定服务。

Service的生命周期

  1. OnStarted方式启动
  2. OnBound方式启动
  • onStartCommand()
    当其他组件,如Activity调用startService()方法请求服务启动的时候,系统调用该方法,一旦该方法执行,服务就启动,并在后台无限期运行。如果开发人员实现该方法,则需要在任务完成时候调用stopSelf()或者stopService()方法停止服务。

  • onBind()
    当其他组件调用bindService方法想与服务绑定的时候,系统调用该方法,在该方法的实现中,开发人员必须通过返回IBinder提供客户端用来与服务通信的接口。该方法必须实现,但是如果不想允许绑定,则应该返回null;

  • onCreate()
    当服务第一创建时,系统调用该方法执行一次性建立(在系统调用onStartCommand()或onBind()方法前)如果服务已经运行,该方法不被调用,从而保证在Android系统中一个Service只有一个实例。

  • onDestroy()
    当服务不再使用并即将销毁时,系统调用该方法。服务应该实现该方法来清除诸如线程,注册监听器,接收者等资源。这是服务受到的最后调用。

Service的声明

开发人员必须在应用程序配置文件中声明全部的Service,方法是在标签中添加子标签。

创建Service

一般方式创建

应用程序组件例如Activity能通过StartService()方法或者传递Intent对象来启动服务,在Intent对象中指定了服务并且包含服务所需要的全部数据,服务使用onStartCommand方法接收Intent。
实验及结果分析:代码见TestService

  • 按下Start Service 启动服务,这是输出结果如下:
  • 按下后退键和Home键,不会输出onDestroy,说明即使按下这两个键,服务仍然在后台运行,可以在Setting->Application 中查看到。
  • 再次点击App图标进入输出如下结果:说明一个Service在Android 系统中只存在一个实例,在已经存在的情况下不会再次调用onCreate创建新的Service。
  • 按下StopService,结果如下:
继承IntentService类

IntentService:这是Service的子类,它每次使用一个工作线程来处理全部启动请求,在不必同时处理多个请求时候,这是最佳选择。开发人员需要实现onHandleIntent方法,它接收每次启动请求的Intent以便完成后台的任务

  • 创建区别于主线程的默认工作线程来执行发送到onStartCommand方法的全部Intent
  • 创建工作队列每次传递一个Intent到onHandleIntent方法实现,这样就不必担心多线程。
  • 所有启动请求处理完毕后停止服务,这样就不要调用stopself方法。
    实现方法:
  • 提供OnBind方法实现,其返回值是null;
  • 提供onStartCommand方法的默认实现,它先发送Intent到默认队列,然后到onHandleIntent方法实现。
    我们要做的工作就是实现onHandleIntent方法,同时由于IntentService并没有提供空参数的构造方法因此必须提供一个构造方法。
    例子:
public class HelloIntentService extends IntentService {
public HelloIntentService() {
super("Hello Service");
}
protected void onHandleIntent(Intent intent) {

long endTime = System.currentTimeMillis()+5*1000;
while(System.currentTimeMillis()<endTime){
synchronized (this) {
try {
wait(endTime-System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("onHandleIntent");
}
}
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "Server Start: "+"flags:"+flags+"start ID:"+startId, Toast.LENGTH_LONG).show();
return super.onStartCommand(intent, flags, startId);
}
public IBinder onBind(Intent intent) {
return null;
}
}
private Button btn = null;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
Intent intent= new Intent(MainActivity.this,HelloIntentService.class);
startService(intent);
}
});
}

实验结果分析:

按下Start IntentService 后输出如下结果:
从结果可以看出,IntentService 会在onHandleIntent方法中处理业务逻辑部分,同时在处理完成后会自动销毁服务,而不需调用stopself/stopService方法。(调用StopService方法不会调用onDestroy方法销毁服务)

继承Service类

使用IntentService类可以简化启动服务的实现,然而,如果需要让服务处理多线程,取代使用工作队列启动请求,也就是说为每次请求创建一个新线程并且立即运行它们,避免等待前一个请求的结束,则可以继承Service类来处理各个Intent
Service:是所有服务的基类,当继承该类的时候,创建新线程来执行服务的全部工作是非常重要的,因为默认服务默认是使用应用程序主线程,这可能会降低应用程序Activity的运行性能。
这种方法要自己使用stopself或者stopService方法结束服务,并且需要自己创建线程来处理服务的内部逻辑
使用这种方式的时候onStartCommand必须返回一个整数。用于表示系统停止服务后如何继续服务。

1. START_NOT_STICKY:如果系统在onStartCommand方法返回后停止服务,则系统不会重新创建服务,除非有PendingIntent要发送。在避免在不必要时运行服务和应用程序能简单地重启任何未完成工作的时候,这是最佳选择。
2. START_STICKY:如果系统在onStartCommand方法返回后停止服务,则系统会重新创建服务并调用onStartCommand方法。但是不重新发送最后的Intent,相反系统使用空Intent调用onStartCommand方法,除非有PeddingIntent来启动服务。此时,这些Intent会发送。这适合多媒体播放器,它们不执行命令但是无限期运行并等待工作。
3. START_REDELIVER_INTENT:如果系统在onStartCommand方法返回后停止服务,重新创建服务并使用发送给服务的最后Intent调用onStartCommand方法,全部PenddingIntent必须依次发送,这适合积极执行应该立即恢复工作的服务。如下载文件。

启动和停止服务

启动服务不能直接调用onStartCommand方法,而是使用Acitivity或者其他应用程序组件通过传递Intent对象到startService()方法启动服务。Android系统自动调用服务的onStartCommand方法并将Intent传递给它,如果服务还没有运行系统首先调用onCreate()方法,接着调用onStartCommand方法。
如果服务没有绑定,StartService方法发送的Intent是应用程序和服务之间唯一的通信方式,然而如果需要获得服务的放回结果,则可以通过启动该服务的客户端能为广播创建PeddingIntent并通过启动服务的Intent发送它,服务接下来能使用广播发送结果。

Started Service方式的服务,系统不会停止或者销毁它,它的生命周期必须自己管理。除非它必须回收系统内存并且在onStartCommand方法返回后服务继续运行,因此服务必须调用stopSelf方法停止自身或者其他组件调用stopService方法停止服务。
stopself(startid)方法可以确保停止服务的请求总是基于最近接收到的启动请求。

创建Bound Service

绑定服务是允许其他应用程序绑定并且与之交互的Service类实现类。为了提供绑定,开发人员必须实现onBind回调方法。该方法返回iBinder对象,它定义了客户端用来与服务交互的程序接口.
客户端能通过bindService方法绑定到服务,此时客户端必须提供ServiceConnection接口的实现类。它监视客户端与服务之间的联系,bindService立刻放回,但是当Android系统创建客户端与服务之间的连接时,它调用ServiceConnection 接口中的onServiceConnection方法来发送客户端用来发送与服务通信的IBinder对象。
多个客户端能够同时连接到服务,但是仅当第一个客户端绑定的时候,系统调用服务的onBind方法来获取Ibinder对象,系统接着发送同一个IBinder对象到其他的绑定的客户端但是不再调用onBind方法,当最后的客户端与服务解除绑定的时候,系统销毁服务。

绑定和解除绑定

应用程序能调用bindService方法绑定到服务,android系统接下来调用服务的onBind方法,它返回IBinder来与服务通信。绑定是异步的bindService方法立刻返回,为了接收IBinder。客户端必须创建ServiceConnection实例然后传递到bindService方法。
只有Activity,Service,Content Provider能够绑定到服务,BroadcastReceiver不能绑定到服务。
如果需要从客户端绑定服务需要完成如下工作:

  1. 实现ServiceConnection这需要重写onServiceConnected和onserviceDisconnected方法
  2. 调用bindService传递ServiceConnection实现。
  3. 当系统调用onServiceConnected方法的时候,就可以使用接口定义的方法。
  4. 调用unbindService解除绑定。

将Service移到前台

在确定哪个应用程序或者应用程序组件可以被终止的时候,Android给正在运行的Service赋予了第二高的优先级,仅仅次于处于激活状态,并且在前台与用户交互的活动Acitivity,在Service需要直接和用户进行交互的情况下,可以将Service的优先级提高到和前台Activity一样高,可以通过调用Service的startForeground方法来将一个服务移到前台,同样可以调用stopForeground来将一个服务移到后台。

int NOTIFICATION_ID = 1;
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 1, intent, 0);
Notification notification = new Notification(R.drawable.ic_launcher, "前台服务", System.currentTimeMillis());
notification.setLatestEventInfo(this, "前台服务", "这个是被移到前台的服务", pi);
notification.flags =notification.flags|Notification.FLAG_ONGOING_EVENT;
startForeground(NOTIFICATION_ID,notification);

提升服务存活可能性的方法

http://blog.csdn.net/mad1989/article/details/22492519

  • onStartCommand方法,返回START_STICKY
  • 提升service优先级

    <service  
    android:name="xxxxxxx"
    android:enabled="true" >
    <intent-filter android:priority="1000" >
    ...............
    </intent-filter>
    </service>
  • 使用startForeground()提升service进程优先级

  • onDestroy方法里重启service
  • 监听系统广播判断Service状态
  • 将应用升级为系统应用
  • Application加上Persistent属性
<application  
android:name="xxxxxxxxxxx"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:persistent="true"
android:theme="@style/AppTheme" >
</application>

Activity 是啥

  • Activity是用于负责提供用户交互的可视化界面,一个应用程序可以有一个或者多个Activity,一般情况下,每个窗口都是满屏的,但是它也可以是一个小的,位于其他窗口之上的浮动窗口。
  • Android允许同时运行多个应用程序,应用程序可以拥有前台进程和后台进程,然而在同一时刻只能由一个应用程序的Activity处于前台。

Activity 生命周期

完整生命周期:

从第一次调用onCreate开始到onDestroy为止,Activity在onCreate中设置所有全局状态以完成初始化,而在onDestroy中释放所有的系统资源:

  • onCreate:当一个Activity首次运行的时候被回调,负责初始化任务,onCreate方法仅具有一个传入参数,即一个Bundle对象,但对于首次启动的Activity而言,这个参数为空,如果这个Activity先前因为内存的原因而被终止,现在又需要重新启动,那么Bundle对象中将包含Activity先前的状态信息,这样它才能被重新初始化。
    主要任务:填充用户UI,将数据绑定到控件,得到Fragment引用,启动Service和定时器

  • onRestoreInstanceState :在onCreate方法完成后调用该函数用于恢复UI状态。
    onRestoreInstanceState 只有在当前视图被系统销毁了时候被调用,因此有时候onSaveInstanceState方法和onRestoreInstanceState方法不一定是成对出现的

  • onDestroy:销毁Activity的时候被调用。
    主要任务:确保清除了onCreate创建的所有资源,并保证所有的外部连接如网络或数据库已经被关闭。

可视生命周期:

从onStart到onStop。在此期间,用户是可见的但是可能还未获得焦点,尚且不能与用户交互。 在这两个方法之间,可以保留用来向用户显示这个Activity所需要的资源,例如,当用户看不到显示的内容的时候,可以在onStart中注册一个BroadCastReceiver广播接收器来监控可能影响的UI变化,而在onStop中来注销,onStart和onStop方法可以随着应用是否被用户可用而被多次调用。

  • onStart:当一个Activity即将显示时被回调,可见生命周期的开始。在这个阶段可以完成如注册专门用于更新用户界面的BroadCast Receiver等工作。
  • onStop:停止Activity的时候被调用。当前Activit处于不可视状态。
    主要任务:暂停或者停止动画,线程,传感器监听器,GPS查找,定时器,启动Service和定时器,或者其他用于更新用户界面的进程,当UI不可见的时候更新它是没有意义的,不但耗费了资源,却没起到实际的作用。当UI再次可用的时候,可使用onStart或者OnRestart方法来恢复或者重启这些线程。
前台生命周期:

从OnResume调用起,至相应的onPause调用为止,在此期间,Activity位于前台最上面并与用户开始进行交互.

  • onResume:这时候该Activity可以接受用户的输入事件,与用户进行交互,同时当Activity由于暂停恢复为活动状态的时候这个方法被调用。调用该方法后,该Activity位于Activity栈顶。在这个方法中不需要重新加载UI状态,因为当要求加载UI状态的时候,它会由onCreate和onRestoreInstanceState方法处理。
  • onPause:暂停的时候被回调,在该方法中通常用于存储持久的数据,在该方法中应该终止在OnrResume方法中播放的所有音频,视频和动画,同时在该方法中还必须解除某些资源,例如:在手动管理而非自动管理情况下的数据库Cursor对象。onPause方法是Activity在后台最后一次能够有机会进行清理工作,释放无需资源的地方,如果没有及时释放,这些资源将有可能不能被彻底释放掉。同时需要保存所有未提交的数据,以免由于程序终止后不再返回而丢失数据。
    应该尽量保证在onResume ,onPause方法中代码较少,以保证前台和后台之间进行切换的时候应用程序能够保持响应。在onPause方法中释放越多的资源,转入后台的Activity被终止的可能性就越小。
  • onSaveInstanceState:这个方法提供了把Activity的UI状态保存在Bundle中的机会,这个Bundle会被传递给onCreate和onRestoreInstanceState方法。一般在这个方法中保存,复选状态,用户焦点,已经输入但未被提交的用户输入,从而保证当Activity下次变成活动的时候,可以呈现出与之前相同的UI。
    这里需要注意的它是只有在非你本来意愿销毁了你的activity的时候被系统调用。但是在activity是被用户主动销毁的情况下(例如我们人为按下back键)不会被调用,下面是一些常见的情况:
  1. 当用户按下HOME键时
  2. 按下电源键灭屏的时候
  3. 从一个Activity A中启动一个新的Activity时。
  4. 在指定configchange属性的情况下切换屏幕方向
    布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动的存储并在activity重新创建的时候自动的恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,将不会存储它的状态。
    onSaveInstanceState()如果被调用,这个方法会在onStop()前被触发,但系统并不保证是否在onPause()之前或者之后触发
典型的Activity生命周期:
启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。
Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity,若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()
Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。
Activity返回前台: onRestart()—>onStart()—>onResume(),再次回到运行状态。
锁定屏与解锁屏幕 只会调用onPause(),而不会调用onStop()方法,开屏后则调用onResume()

Activity任务堆栈

直接上图不解释:

我们可以通过android:launchMode = “standard|singleInstance|singleTask|singleTop”来控制Acivity任务栈。

standard : 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。
singleTop : 这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时Activity的onNewIntent()方法会被回调.如果Activity已经存在但是不在栈顶,那么作用于standard模式一样.
singleTask: 创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈.然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,并调用onNewIntent(),并且singleTask会清理在当前Activity上面的所有Activity.
singleInstance : 这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了

如果对上面的不大理解可以参考如下的文章,说的很详细,为了避免作者把文章取消所以把重点的引用如下:
http://www.cnblogs.com/xiaoQLu/archive/2011/09/29/2195742.html

standard:Activity的默认加载方法,即使某个Activity在Task栈中已经存在,另一个activity通过Intent跳转到该activity,同样会新创建一个实例压入栈中。例如:现在栈的情况为:A B C D,在D这个Activity中通过Intent跳转到D,那么现在的栈情况为: A B C D D 。此时如果栈顶的D通过Intent跳转到B,则栈情况为:A B C D D B。此时如果依次按返回键,D  D C B A将会依次弹出栈而显示在界面上。
singleTop:如果某个Activity的Launch mode设置成singleTop,那么当该Activity位于栈顶的时候,再通过Intent跳转到本身这个Activity,则将不会创建一个新的实例压入栈中。例如:现在栈的情况为:A B C DDLaunch mode设置成了singleTop,那么在D中启动Intent跳转到D,那么将不会新创建一个D的实例压入栈中,此时栈的情况依然为:A B C D。但是如果此时B的模式也是singleTop,D跳转到B,那么则会新建一个B的实例压入栈中,因为此时B不是位于栈顶,此时栈的情况就变成了:A B C D B。
singleTask:如果某个Activity是singleTask模式,那么Task栈中将会只有一个该Activity的实例。例如:现在栈的情况为:A B C D。B的Launch mode为singleTask,此时D通过Intent跳转到B,则栈的情况变成了:A B。而CD被弹出销毁了,也就是说位于B之上的实例都被销毁了。
singleInstance:将Activity压入一个新建的任务栈中。例如:Task栈1的情况为:A B CC通过Intent跳转到D,而DLaunch mode为singleInstance,则将会新建一个Task栈2。此时Task栈1的情况还是为:A B C。Task栈2的情况为:D。此时屏幕界面显示D的内容,如果这时D又通过Intent跳转到D,则Task栈2中也不会新建一个D的实例,所以两个栈的情况也不会变化。而如果D跳转到C,则栈1的情况变成了:A B C C,因为CLaunch mode为standard,此时如果再按返回键,则栈1变成:A B C。也就是说现在界面还显示C的内容,不是D

Activity 状态

上面介绍了Activity的堆栈接下来就来介绍下,Activity的状态,Activity状态是由其在Activity堆栈的位置决定的:

Android应用程序生命周期

Android应用不能控制它们自己的生命周期,它是由Android运行时控制的,默认情况下每个应用程序都是通过它们自己的进程运行的,每个进程都运行在独立的Dalvik实例中,每个应用程序的内存和进程管理都是由运行时专门进行处理的。
回收资源的时候,进程被终止的顺序是由它们的应用优先级所决定的,一个应用程序的优先级等同于它的优先级最高的组件的优先级。
当两个应用程序优先级相同的时候,在较低优先级状态运行时间最长的进程将会首先被终止。
进程的优先级也受进程间依赖性的影响,如果一个应用程序依赖于第二个应用程序所提供的Service或者Content Provider,那么第二个应用程序至少拥有与它所支持的这个应用程序相同的优先级。

进程状态

  1. 活动进程:指的是有组件正在和用户进行交互的应用程序进程,Android尝试回收其他应用状态资源来支持这些进程运行。

活动的进程包括:

  • 处于活动状态的Activity。
  • 正在运行,且已被标记为前台运行的Service
  • 正在执行onCreate ,onStart,onDestroy事件处理的Service。
  • 正在执行onReceiver 事件处理程序的BroadCast Receiver
  1. 可见进程:可见但是非活动的进程是指那些包含“可见”Acitivity的进程。

  2. 启动服务进程:已经启动的Service的进程,因为后台Service没有直接和用户交互,所以它的优先级要比可见进程或者前台Service低。

  3. 后台进程:不可见并且没有任何正在运行的Service的Activity进程,通常会有大量的后台进程。
  4. 空进程:为了提高系统整体性能,Android经常在应用程序的生存周期结束后仍然把它们保存在内存中,Android通过维护这个缓存来减少应用程序被再次启动时的启动时间。

Activity的创建

  1. 创建一个类继承自Activity,并重写相应的方法。
  2. 编写布局文件,设置布局。
  3. 在Android Manifest中配置Activity。

Activity属性

Android 开发是基于Java环境开发的,但是你如果写过测试用例的话你就会有个感觉就是,它和Java还是有很大区别的,比如你不能随便new一个类让它运行起来,我们在测试用例中有的使用要费劲心思来创建和待测试一样的运行环境,这里要介绍的Context就是一种上下文环境,下面内容是来自我在入职初期学习时候做的PPT来的,所以比较简洁不过大概涵盖了Context所涉及的知识点。
如上面提到的Android是基于组件,每个组件的运行都要有一个环境才能够正常工作,所以不是简单地New一个对象调用就能创建实例了,而是要有它们各自的上下文环境Context。在Android开发初期大家可能最难理解的就是这个Context了吧,至少我当时是萌逼状态的。

Context的作用

  • 可以通过 Context识别调用者的实例。
    (例如:TextView label = new TextView(this)
    意味着view拥有一个指向Activity的引用,进而引用Activity占有的资源).
  • 管理应用程序特定的配置细节
  • 管理应用程序范围的操作和数据
  • 访问当前包的资源(getResources、getAssets)
  • 启动其他组件(Activity、Service、Broadcast)
  • 得到各种系统服务(getSystemService)
  • 使用Application Context来访问设置和资源可以在多个Activity实例中共享

Application Context

Application-Context的生命周期是整个应用,Application Context可以通过Context.getApplicationContext或者Activity.getApplication方法获取。
在Activity或者Service中可以通过getApplication()函数获得,不管通过何种方法在哪里获得的,在一个进程内,总是获得到同一个实例。

Application通常有如下常用用途:

1.获得当前应用的主题,资源文件中的内容等
2.使用Application同步Activity之间的状态,例子见备注部分:

实现在任何位置获取Applcation Context 方法:

在Android中只有Activity, Provider等组件中才能使用API获取Application Context,但是在某些工具类中往往需要它来访问资源。这就需要我们自定义一个Application类来解决这个问题:

public class MyApplication extends Application {  
private static MyApplication instance;
public static MyApplication getInstance() {
return instance;
}
public void onCreate() {
super.onCreate();
instance = this;
}
}

为了能正确地调用MyApplication.getInstance(),需要在manifest中中加入name=”mypackage.MyApplication”。

获取Context的方式

  • 可以使用getApplicationContext()方法从当前进程中获取Context,这个方法在例如Activity或者Service中能够找到。通常我们使用Context对象时,要优先考虑这个全局的进程Context。

    Context context = getApplicationContext()
  • getContext()当前Activity对象的Context对象

  • Activity.this 返回当前的Activity实例,这个和上一个有点类似。

使用应用程序Context

获得了一个应用程序Context后,就可以用它来访问应用程序范围的功能和服务了,这些功能和服务包括:

  • 获取应用程序资源,例如字符串,图像和XML文件:

    String greeting = getResource().getString(R.string.hello);
  • 访问应用程序首选项使用getSharedPreferences来访问共享的应用程序首选项

  • 管理私有的应用程序文件以及目录
  • 获取未编译的应用程序组件使用getAssets方法来获取应用程序的资源
  • 获取系统服务
  • 管理私有的应用程序数据库
  • 使用应用程序权限

应用程序的Context数目

在Android中 Context的子类有:Application,Activity,Service,所以:

Context数量 = Activity数量 + Service数量 + 1 (Application Context)

错误使用带来的问题:

Context 错误使用会带来很严重的内存泄漏的问题,这个在内存优化部分有做过介绍,比如下面的例子,这是一个非常简单的懒汉式单例:

public class Singleton {

private Context mContext;
private static Singleton instance;
private Singleton(Context context) {
this.mContext = context;
}

public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}

我们知道单例的生命周期要长于一般的组件,一般是常驻内存的,如果在创建这个单例的时候传入的是Activity的上下文那么除了应用退出,否则Activity的上下文会一直被单例持有,所以导致即使Activity被销毁掉,也不能被GC回收,从而导致了内存泄漏。

使用Context要注意的几点

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:

  • 在开发中尽量使用Application的Context,特别是生命周期长的情况下。不要让生命周期长于组件的对象持有组件的引用。
  • 非静态内部类会隐式持有外部类实例的引用,如果需要使用内部类那么需要将外部实例引用作为弱引用持有,从而避免内存泄漏。

  1. add-ons: 这里面保存着附加库,比如Google Maps。
  2. docs: 这里面是Android SDK API参考文档,所有的API都可以在这里查到。
  3. tools: 作为SDK根目录下的tools文件夹,这里包含了重要的工具,比如ddms用于启动Android调试工具,比如logcat、屏幕截图和文件管理器,而draw9patch则是绘制android平台的可缩放png图片的工具,sqlite3可以在PC上操作SQLite数据库, 而monkeyrunner则是一个不错的压力测试应用,模拟用户随机按键,mksdcard则是模拟器SD映像的创建工具,emulator是 Android SDK模拟器主程序,不过从android 1.5开始,需要输入合适的参数才能启动模拟器,traceview作为android平台上重要的调试工具。
  4. extas: 附件文档
  5. platforms: 是每个平台的SDK真正的文件,里面会根据APILevel划分的SDK版本,这里就以Android2.2来说,进入后有 一个android-8的文件夹,android-8进入后是Android2.2 SDK的主要文件,其中ant为ant编译脚本,data保存着一些系统资源,images是模拟器映像文件,skins则是Android模拟器的皮肤,templates是工程创建的默认模板,android.jar则 是该版本的主要framework文件,tools目录里面包含了重要的编译工具,比如aapt、aidl、逆向调试工具dexdump和编译脚本dx。
  6. platform-tools保存着一些通用工具,比如adb、和aapt、aidl、dx等文件,这里和platforms目录中tools文件夹有些重复,主要是从android2.3开始这些工具被划分为通用了。
  7. samples是Android SDK自带的默认示例工程
  8. system-images 系统镜像
  9. sources Android 资源文件夹
  10. temp 缓存目录

想必大家接触Android的时候最早接触的图就是下面这张吧,它是Android整个架构图,虽然Android 系统已经发展到Android M,并且Android N 也快发布,但是整个架构还是不变的。

  • Linux内核 Android核心系统服务依赖于Linux内核,如安全性、内存管理、进程管理、网络协议栈和驱动模型。Linux内核也是作为硬件与软件栈的抽象层。驱动:显示驱动、摄像头驱动、键盘驱动、WiFi驱动、Audio驱动、flash内存驱动、Binder(IPC)驱动、电源管理等。
  • 系统库和Android运行时

系统库包括九个子系统,分别是图层管理、媒体库、SQLite、OpenGL ES、FreeType、WebKit、SGL、SSL和libc。
Android运行时包括核心库和Dalvik虚拟机,前者既兼容了大多数Java语言所需要调用的功能函数,又包括了Android的核心库,比如android.os、android.net、android.media等等。
后者是一种基于寄存器的java虚拟机,Dalvik虚拟机主要是完成对生命周期的管理、堆栈的管理、线程的管理、安全和异常的管理以及垃圾回收等重要功能。

系统库:

libc            C语言标准库
Surface Menager 主要管理多个应用程序同时执行时,各个程序之间的显示与存储
Media Framework 系统多媒体库
SQLite 关系型数据库
OpenGL ES 3D效果支持
FreeType 位图及矢量库
WebKit Web 浏览器引擎
SGL 2D图形引擎
SSL 位于TCP/IP与各种应用层协议之间,为数据通信提供支持
  • 应用程序框架层

该层是Android应用开发的基础,开发人员大部分情况是在和它打交道。应用程序框架层包括活动管理器、窗口管理器、内容提供者、视图系统、包管理器、电话管理器、资源管理器、位置管理器、通知管理器和XMPP服务十个部分。在Android平台上,开发人员可以完全访问核心应用程序所使用的API框架。并且,任何一个应用程序都可以发布自身的功能模块,而其他应用程序则可以使用这些已发布的功能模块。基于这样的重用机制,用户就可以方便地替换平台本身的各种应用程序组件。

Activity Manager 活动管理器用于管理应用程序生命周期
View System 视图管理器,用来构建应用程序如列表,表格,文本框,按钮等
Window Manager窗口管理者
Content Provider 内容提供者
Notification Manager 通知管理者,用来设置在状态栏中显示的提示信息
Package Manager 包管理者,用来对Android系统内的应用程序进行管理
Telephony Manager 电话管理者,用来对联系人及通话记录等信息进行管理
Resource Manager 资源管理者,用来提供非代码资源的访问
Location Manager 位置管理者,用来提供使用者的当前位置等信息
XMPP Service Service服务
  • 应用程序层

该层提供一些核心应用程序包,例如电子邮件、短信、日历、地图、浏览器和联系人管理等。同时,开发者可以利用Java语言设计和编写属于自己的应用程序,而这些程序与那些核心应用程序彼此平等、友好共处。

对于Android系统架构推荐如下两篇博客:
http://developer.51cto.com/art/201001/180207.htm
http://mobile.51cto.com/android-235496.htm

Delvik虚拟机以及ART

这篇博客将会在以后的时间添加这一部分,先占个坑!

硬件设备:
RapsBerry 3
操作系统:
Rapsbian OS jessie

  1. Setup ROS Repositories
$ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu jessie main" > /etc/apt/sources.list.d/ros-latest.list'
$ wget https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -O - | sudo apt-key add -

2.更新软件源

$ sudo apt-get update
$ sudo apt-get upgrade

3.Install Bootstrap Dependencies

$ sudo apt-get install python-pip python-setuptools python-yaml python-distribute python-docutils python-dateutil python-six
$ sudo pip install rosdep rosinstall_generator wstool rosinstall

4.Initializing rosdep

$ sudo rosdep init
$ rosdep update

Installation

Now, we will download and build ROS Indigo.

Create a catkin Workspace

In order to build the core packages, you will need a catkin workspace. Create one now:

$ mkdir ~/ros_catkin_ws
$ cd ~/ros_catkin_ws

Next we will want to fetch the core packages so we can build them. We will use wstool for this. Select the wstool command for the particular variant you want to install:

ROS-Comm: (recommended) ROS package, build, and communication libraries. No GUI tools.

$ rosinstall_generator ros_comm --rosdistro indigo --deps --wet-only --exclude roslisp --tar > indigo-ros_comm-wet.rosinstall
$ wstool init src indigo-ros_comm-wet.rosinstall


This will add all of the catkin or wet packages in the given variant and then fetch the sources into the ~/ros_catkin_ws/src directory. The command will take a few minutes to download all of the core ROS packages into the src folder. The -j8 option downloads 8 packages in parallel. 




Resolve Dependencies

Before you can build your catkin workspace, you need to make sure that you have all the required dependencies. We use the rosdep tool for this, however, a couple of dependencies are not available in the repositories. They must be manually built first.

Unavailable Dependencies

Following packages are not available for Raspbian:

Raspbian Wheezy: libconsole-bridge-dev, liburdfdom-headers-dev, liburdfdom-dev, liblz4-dev, collada-dom-dev

Raspbian Jessie: collada-dom-dev

The following packages are needed for each ROS variant:

Ros_Comm: libconsole-bridge-dev, liblz4-dev

Desktop: libconsole-bridge-dev, liblz4-dev, liburdfdom-headers-dev, liburdfdom-dev, collada-dom-dev

The required packages can be built from source in a new directory:

$ mkdir ~/ros_catkin_ws/external_src
$ sudo apt-get install checkinstall cmake
$ sudo sh -c 'echo "deb-src http://mirrordirector.raspbian.org/raspbian/ testing main contrib non-free rpi" >> /etc/apt/sources.list'
$ sudo apt-get update

libconsole-bridge-dev:

$ cd ~/ros_catkin_ws/external_src
$ sudo apt-get build-dep console-bridge
$ apt-get source -b console-bridge
$ sudo dpkg -i libconsole-bridge0.2*.deb libconsole-bridge-dev_*.deb



liblz4-dev:

$ cd ~/ros_catkin_ws/external_src
$ apt-get source -b lz4
$ sudo dpkg -i liblz4-*.deb



Resolving Dependencies with rosdep

The remaining dependencies should be resolved by running rosdep:

Raspbian Wheezy:

$ cd ~/ros_catkin_ws
$ rosdep install –from-paths src –ignore-src –rosdistro indigo -y -r –os=debian:wheezy

Raspbian Jessie:

$ cd ~/ros_catkin_ws
$ rosdep install –from-paths src –ignore-src –rosdistro indigo -y -r –os=debian:jessie

Building the catkin Workspace

Once you have completed downloading the packages and have resolved the dependencies, you are ready to build the catkin packages.

Invoke catkin_make_isolated:

$ sudo ./src/catkin/bin/catkin_make_isolated –install -DCMAKE_BUILD_TYPE=Release –install-space /opt/ros/indigo

Note: This will install ROS in the equivalent file location to Ubuntu in /opt/ros/indigo however you can modify this as you wish.

For rviz, you will also have to apply this patch.

Should the compilation fail with an “internal compiler error”, it may be because you’re out of memory. A quick fix for this is to add swap space to the Pi and recompile. If the error persists try building with the -j2 option instead of the default -j4 option:

$ sudo ./src/catkin/bin/catkin_make_isolated –install -DCMAKE_BUILD_TYPE=Release –install-space /opt/ros/indigo -j2

Now ROS should be installed! Remember to source the new installation:

$ source /opt/ros/indigo/setup.bash

Or optionally source the setup.bash in the ~/.bashrc, so that ROS environment variables are automatically added to your bash session every time a new shell is launched:

$ “source /opt/ros/indigo/setup.bash” >> ~/.bashrc

我们在介绍Hello World的时候介绍了roscore 命令,但是并没有介绍它具体做了什么操作:
其实在运行这个命令的时我们启动了如下的组件:

  • ROS Master
  • ROS 参数服务器
  • Rosout log节点
    前两者已经在上述的章节进行了介绍,至于roscore节点它是用于从其他ROS节点中收集Log信息,并将其存储在log文件中。

下面就先运行roscore命令看下效果,运行命令后会弹出如下的Log:

//产生的Log文件
... logging to /home/pi/.ros/log/674306a0-06fc-11e6-8a8b-b827eb2bb378/roslaunch-raspberrypi-2054.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

//这个命令将会启动roscore.xml ROS启动文件,这个文件会自动启动ros master和ros参数服务器,下面这个地方显示的就是ROS参数服务器的地址和端口
started roslaunch server http://raspberrypi:41429/
ros_comm version 1.11.18

//这个部分显示的是ros版本以及ros版本号信息的参数
SUMMARY
========

PARAMETERS
* /rosdistro: indigo
* /rosversion: 1.11.18

NODES

//显示ROS_MASTER_URI信息
auto-starting new master
process[master]: started with pid [2065]
ROS_MASTER_URI=http://raspberrypi:11311/

setting /run_id to 674306a0-06fc-11e6-8a8b-b827eb2bb378
process[rosout-1]: started with pid [2078]

//启动rosout服务
started core service [/rosout]