Android 服务 Service

来源:互联网 发布:售假仅退款淘宝介入 编辑:程序博客网 时间:2024/05/21 19:21

    • 简介
      • 分类
    • 服务的基本用法
      • 定义一个服务Service抽象类
      • 启动和停止服务
      • 活动和服务进行通信bindService
      • 判断service是否在运行
    • Android中程序与Service交互的方式交互方式转
      • 广播交互
      • 共享文件交互
      • Mssenger信使交互
      • 自定义接口交互
      • AIDL交互
    • 服务的生命周期
      • BindService的生命周期
      • onStartCommand
      • 两种启动Service的生命周期对比
    • 前台服务
      • 创建
    • 使用IntentService
    • 总结

简介

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
另外,也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
service就是android系统中的服务,它有这么几个特点:它无法与用户直接进行交互、它必须由用户或者其他程序显式的启动、它的优先级比较高,它比处于前台的应用优先级低,但是比后台的其他应用优先级高,这就决定了当系统因为缺少内存而销毁某些没被利用的资源时,它被销毁的概率很小哦。

分类

Service分为本地服务(LocalService)和远程服务(RemoteService)。
1、本地服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被Kill后,服务便会终止。

2、远程服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。

按使用方式可以分为以下三种:

1、startService 启动的服务:主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService;

2、bindService 启动的服务:该方法启动的服务可以进行通信。停止服务使用unbindService;

3、startService 同时也 bindService 启动的服务:停止服务应同时使用stepService与unbindService

服务的基本用法

定义一个服务Service——抽象类

public class MyService extends Service {    @Override    public IBinder onBind(Intent intent) {        return null;    }}

onBind方法是Service中唯一一个抽象方法,必须在子类中实现。而且一般至少会重写了onCreate()、onStartCommand()和 onDestroy()这三个方法。

public class MyService extends Service {    @Override    public IBinder onBind(Intent intent) {        return null;    }    @Override    public void onCreate() {        super.onCreate();    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        return super.onStartCommand(intent, flags, startId);    }    @Override    public void onDestroy() {        super.onDestroy();    }}

onCreate()方法会在服务创建的时候调用,onStartCommand()方法会在每次服务启动的时候调用,onDestroy()方法会在服务销毁的时候调用。 onCreate()方法是在服务第一次创建的时候调用的,而 onStartCommand()方法则在每次启动服务的时候都会调用。

启动和停止服务

//在activity中Intent startIntent = new Intent(this, MyService.class);startService(startIntent); // 启动服务Intent stopIntent = new Intent(this, MyService.class);stopService(stopIntent); // 停止服务

那服务有没有什么办法让自已停止下来呢?当然可以,只需要在 MyService 的任何一个位置调用 stopSelf()方法就能让这个服务停止下来了。

活动和服务进行通信(bindService)

当应用程序中的activity或其它组件需要与服务进行交互,或者应用程序的某些功能需要暴露给其它应用程序时,你应该创建一个bound服务,并通过进程间通信(IPC)来完成。

在进行服务绑定的时,其flags有:

Context.BIND_AUTO_CREATE

  表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁 

Context.BIND_DEBUG_UNBIND  

  通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用

Context.BIND_NOT_FOREGROUND  

  表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行。

public class MyService extends Service {    private DownloadBinder mBinder = new DownloadBinder();    class DownloadBinder extends Binder {        public void startDownload() {            Log.d("MyService", "startDownload executed");        }        public int getProgress() {            Log.d("MyService", "getProgress executed");            return 0;        }    }    @Override    public IBinder onBind(Intent intent) {        return mBinder;        //如果这边不返回一个IBinder的接口实例,那么ServiceConnection中的onServiceConnected就不会被调用         //那么bind所具有的传递数据的功能也就体现不出来~\(≧▽≦)/~啦啦啦(这个返回值是被作为onServiceConnected中的第二个参数的)     }    ……}
//在活动中调用/*             * 只有在MyService中的onBind方法中返回一个IBinder实例才会在Bind的时候             * 调用onServiceConnection回调方法             * 第二个参数service就是MyService中onBind方法return的那个IBinder实例,可以利用这个来传递数据             */ private ServiceConnection connection = new ServiceConnection() {    @Override    public void onServiceDisconnected(ComponentName name) {    }       @Override    public void onServiceConnected(ComponentName name, IBinder service) {        downloadBinder = (MyService.DownloadBinder) service;        downloadBinder.startDownload();        downloadBinder.getProgress();    }    @Override     publicvoid onServiceDisconnected(ComponentName name) {     /* SDK上是这么说的:        * This is called when the connection with the service has been unexpectedly disconnected        * that is, its process crashed. Because it is running in our same process, we should never see this happen.        * 所以说,只有在service因异常而断开连接的时候,这个方法才会用到*/                 // TODO Auto-generated method stub                 sc = null;                 Log.i("TAG", "onServiceDisconnected : ServiceConnection --->"                         + sc);             } };@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    ……    bindService = (Button) findViewById(R.id.bind_service);    unbindService = (Button) findViewById(R.id.unbind_service);    bindService.setOnClickListener(this);    unbindService.setOnClickListener(this);}case R.id.bind_service:    Intent bindIntent = new Intent(this, MyService.class);    bindService(bindIntent, connection, BIND_AUTO_CREATE); //  绑定服务    break;case R.id.unbind_service:    unbindService(connection); //  解绑服务    break;

BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务。这会使得 MyService 中的 onCreate()方法得到执行,但 onStartCommand()方法不会执行。

判断service是否在运行

private boolean isServiceRunning() {    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);  {        if ("com.example.demo.BindService".equals(service.service.getClassName()))   {            return true;        }    }    return false;}

Android中程序与Service交互的方式——交互方式(转)

写的太好的文章,转载过来:http://blog.csdn.net/yihongyuelan/article/details/7216188

广播交互、共享文件交互、Mssenger(信使)交互、自定义接口交互、AIDL交互。

广播交互

提到Activity与Service的交互,可能狠多人首先想到的就是BroadCast——广播。在Android中,广播是系统提供的一种很好的交互方式。比如:在电池电量过低,开机完成等情况下,系统都会发出相应的系统广播,我们的应用程序只需要注册相应的广播接收器,就可以接收到这些系统的广播。同时,我们也可以定义自己的广播,这样在不同的Activity、Service以及应用程序之间,就可以通过广播来实现交互。我们通过模拟应用程序后台下载的情况来分析Service与Activity的交互方式。

1.1. 实现原理

Server端将目前的下载进度,通过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上。

1.2. 实现步骤

1.2.1 在Client端中通过startService()啟动Service。

if(v == startBtn){      Log.i(TAG, "start button clicked...pid: "+Process.myPid());      mIntent.setClass(BroadCastService.this, DownLoadService.class);      startService(mIntent);  }  //这里的mIntent = new Intent();Process.myPid()方法可以获取当前进程的ID号。

1.2.2 DownLoadService接到启动的命令之后,执行onCreate()方法,并在其中开启timer计数模拟下载。

@Override  public void onCreate() {      super.onCreate();      Log.i(TAG, "DownLoadService.onCreate()...pid: "+Process.myPid());      intent = new Intent("com.seven.broadcast");      mTimer = new Timer();      mTimer.schedule(new MyTimerTask(), 0 , TIME * 1000);  }  //这里的intent是Server端向Client端传送数据用的,使用的action是”com.seven.broadcast”,

Client端只有註册了相应action才能够接收到Server端的广播,并解析其中的内容。Process.myPid()是获取当前进程的ID。

1.2.3 在Server端的timer计数其中发送广播,告知Client端目前下载进度。

class MyTimerTask extends TimerTask{      @Override      public void run() {          if(i==100){              i=0;          }          intent.putExtra("CurrentLoading", i);          sendBroadcast(intent);          i++;          Log.e(TAG, "i= "+i);      }  }  通过intent.putExtra(key,value);设置intent的值,然后通过sendBroadcast(intent)2方法,将广播发送出去。

1.2.4 在Client端通过匿名内部类的方式实例化BroadcastReceiver并覆写其中的onReceive()方法。

BroadcastReceiver receiver = new BroadcastReceiver() {      @Override      public void onReceive(Context context, Intent intent) {          if(MYACTION.equals(intent.getAction())){              Log.i(TAG, "get the broadcast from DownLoadService...");              curLoad = intent.getIntExtra("CurrentLoading", ERROR);              mHandler.sendMessage(mHandler.obtainMessage());          }      }  };  

在onReceive()方法中,判断是否为Server端发送的广播,如果是则对广播中携带的intent数据进行解包处理。这裡也可以单独写一个类继承自BroadcastReceiver,在其中覆写onReceive()方法,在Client端中实例化其对象,同样可以达到相应的效果,这样做可以为后面实现静态注册广播。

1.2.5 更新主介面下载进度。

Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);          Log.i(TAG, "current loading: "+curLoad);          if(curLoad<0||curLoad>100){              Log.e(TAG, "ERROR: "+curLoad);              return;          }          mProgressBar.setProgress(curLoad);          mTextView.setText(curLoad+"%");      }  };  

这里对获取到的进度进行了一次判断,如果获取到的值没有异常,那么将会显示到界面,并更新进度条的进度,如果异常则返回。

1.2.6 一定要对Broadcast进行注册和取消注册。只有注册之后相应的broadcast之后才能接收到广播注册方法有两种。

动态注册/取消注册:
动态註册可以随时註册随时取消。

@Override  protected void onResume() {      super.onResume();      Log.i(TAG, "register the broadcast receiver...");      IntentFilter filter = new IntentFilter();      filter.addAction(MYACTION);      registerReceiver(receiver, filter);  }  @Override  protected void onDestroy() {      super.onDestroy();      Log.i(TAG, "unregister the broadcast receiver...");      unregisterReceiver(receiver);  }  

静态註册:

<receiver android:name="MyBroadcastReceiver">      <intent-filter>          <action android:name="com.seven.broadcast" />      </intent-filter>  </receiver>  

注:这里的MyBroadcastReceiver是一个继承自BroadcastReceiver的类。静态注册只要注册了一次那么只要该程序没有被卸载那么该广播将一直有效。
最后贴出整个AndroidManifest.xml文件

<application android:icon="@drawable/icon" android:label="@string/app_name">      <activity android:name=".BroadCastService"                android:label="@string/app_name">          <intent-filter>              <action android:name="android.intent.action.MAIN" />              <category android:name="android.intent.category.LAUNCHER" />          </intent-filter>      </activity>      <service android:name="DownLoadService" android:process=":remote"/>  </application>  这里的android:process =”:remote”可以使该Service运行在单独进程中,从而可以模拟跨进程通信。

1.3 小结

通过广播的方式实现Activity与Service的交互操作简单且容易实现,可以胜任简单级的应用。但缺点也十分明显,发送广播受到系统制约。系统会优先发送系统级广播,在某些特定的情况下,我们自定义的广播可能会延迟。同时在广播接收器中不能处理长耗时操作,否则系统会出现ANR即应用程序无响应。

共享文件交互

这里提到的共享文件指的是Activity和Service使用同一个文件来达到传递数据的目的。使用SharedPreferences来实现共享,当然也可以使用其它IO方法实现,通过这种方式实现交互时需要注意,对于文件的读写的时候,同一时间只能一方读一方写,不能两方同时写。

2.1 实现原理

Server端将当前下载进度写入共享文件中,Client端通过读取共享文件中的下载进度,并更新到主界面上。

2.2 实现步骤

2.2.1 在Client端通过startService()啟动Service。

if(startSerBtn==v){      Log.i(TAG, "Start Button Clicked.");      if(intent!=null){      startService(intent);      timer.schedule(new MyTimerTask(), 0, TIME * 1000);      }  }  

这里的intent = new Intent()2只是为了启动Server端。

2.2.2 Server端收到启动intent之后执行onCreate()方法,并开启timer,模拟下载,以及初始化SharedPreferences对象preferences。

@Override  public void onCreate() {      super.onCreate();      Log.i(TAG, "DownLoadService.onCreate()...");      preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);      timer = new Timer();      timer.schedule(new MyTimerTask(), 0, TIME*1000);  }  

通过preferences=getSharedPreferences(String,MODE)2可以在/data/data/com.seven.servicetestdemo/shared_prefs文件夹下建立相应的xml文件。

2.2.3 开始计数并将下载进度写入shared_prefs文件夹下的xml文件中,内容以键值对的方式保存。

class MyTimerTask extends TimerTask{      @Override      public void run() {          setCurrentLoading();          if(100==i){              i=0;          }          i++;      }         }     private void setCurrentLoading() {      preferences.edit().putInt("CurrentLoading", i).commit();  }  

对於SharedPreferences的使用需要注意一下几点:
首先,使用sharedPreferences前需要获取文件引用。

preferences = getSharedPreferences(“CurrentLoading_SharedPs”, 0);

其次,使用sharedpreferences写数据方式。

preferences.edit().putInt(“CurrentLoading”, i).commit();

最后,读取数据的方式。

int couLoad = preferences.getInt(“CurrentLoading”, 0);

2.2.4 Client端通过读取/data/data/com.seven.servicetestdemo/shared_prefs文件夹下的xml文件,并取得里面的键值对,从而获取到当前的下载进度,并更新到主界面上。

Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);          int couLoad = preferences.getInt("CurrentLoading", 0);          mProgressBar.setProgress(couLoad);          currentTv.setText(couLoad+"%");      }   };  

2.3 小结
因為方法简单,因此就不贴出AndroidManifest.xml文件了。对於这种方式实现Activity与Service的交互,可以说很方便,就像使用管道,一个往裡写,一个往外读。但这种方式也有缺陷,写入数据较为复杂以及数据量较大时,就有可能导致写入与读数据出不一致的错误。同时因为经过了一个中转站,这种操作将更耗时。

Mssenger(信使)交互

Messenger翻译过来指的是信使,它引用了一个Handler对象,别人能够向它发送消息(使用mMessenger.send(Message msg)方法)。该类允许跨进程间基于Message通信,在服务端使用Handler创建一个 Messenger,客户端只要获得这个服务端的Messenger对象就可以与服务端通信了。也就是说我们可以把Messenger当做Client端与Server端的传话筒,这样就可以沟通交流了。

3.1 实现原理
在Server端与Client端之间通过一个Messenger对象来传递消息,该对象类似于信息中转站,所有信息通过该对象携带。

(1). 在Server端创建信使对象。

   mMessenger = new Messenger(mHandler)
public Messenger(Handler target) {      mTarget = target.getIMessenger();  }  

其中mTarget是一个IMessenger的aidl接口,getIMessenger()做了什么呢

final IMessenger getIMessenger() {      synchronized (mQueue) {          if (mMessenger != null) {              return mMessenger;          }          mMessenger = new MessengerImpl();          return mMessenger;      }  }  private final class MessengerImpl extends IMessenger.Stub {      public void send(Message msg) {          msg.sendingUid = Binder.getCallingUid();          Handler.this.sendMessage(msg);      }  }  

可以看到getIMessenger()返回了一个IMessenger类型的Binder对象,Messenger本身也是通过AIDL实现的。而且我们在调用send(msg)方法的时候,其实是调用了与这个Binder关联的Handler的sendMessage()方法,继而会触发这个Handler的handleMessage()的调用。

通过上面的分析,大概能猜出来,Activity端就是拿到这个IMessenger类型的Binder的代理对象,通过代理对象的send()方法来发送消息到Service中。

(2). Client端使用bindService()绑定Server端。

(3). Server端的onBind()方法返回一个binder对象。

   return mMessenger.getBinder();

(4). Client端使用返回的binder对象得到Server端信使。

public void onServiceConnected(ComponentName name, IBinder service) {                  rMessenger = new Messenger(service);                     ......   }  

这里虽然是new了一个Messenger,但我们查看它的实现:

public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target);  }   

发现它的mTarget是通过AIDL得到的,实际上就是远程创建的那个。

(5). Client端可以使用这个Server端的信使对象向Server端发送消息。

 rMessenger.send(msg)

这样Server端的Handler对象就能收到消息了,然后可以在其handlerMessage(Message msg)方法中进行处理。经过这5个步骤之后只有Client端向Server端发送消息,这样的消息传递是单向的,那么如何实现消息的双向传递呢?

    首先需要在第5步做修改,在send(msg)前通过msm.replyTo = mMessenger将Client端自己的信使设置到消息中,这样Server端接收到消息时同时也得到了Client端的信使对象,然后Server端也可以通过使用得到的Client端的信使对象来项Client端发送消息 cMessenger = msg.replyTo2  cMessenger.send(message);   这样即完成了从Server端向Client端发送消息的功能,这样Client端可以在自己的Handler对象的handlerMessage()方法中接收服务端发送来的message进行处理。

3.3 实现步骤
3.3.1 创建并初始化Server端的信使对象。

private Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);          switch (msg.what) {          case TEST:              Log.e(TAG, "Get Message from MainActivity.");              cMessenger = msg.replyTo;              mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);              break;              default:                  break;              }          }         };  //It's the messenger of server  private Messenger mMessenger = new Messenger(mHandler);

3.3.2 在Client端使用bindService()方法绑定Server端。

private void doBindService(){          Log.i(TAG, "doBindService()...");          mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);//if bind success return true          Log.e(TAG, "Is bind: "+mIsBind);  }  

3.3.3 在Server端的onBind()方法中返回一个binder对象。

@Override  public IBinder onBind(Intent intent) {      Log.i(TAG, "MessengerService.onBind()...");      return mMessenger.getBinder();  }  

这裡的mMessenger就是Server端的信使对象。

3.3.4 Client端使用ServiceConnected()方法来获取Server端的信使对象。

private ServiceConnection serConn = new ServiceConnection() {         @Override      public void onServiceDisconnected(ComponentName name) {          Log.i(TAG, "onServiceDisconnected()...");          rMessenger = null;      }             @Override      public void onServiceConnected(ComponentName name, IBinder service) {          Log.i(TAG, "onServiceConnected()...");      rMessenger = new Messenger(service);//get the object of remote service      mMessenger = new Messenger(mHandler);//initial the object of local service      sendMessage();      }  };  

获取Server端的信使对象的同时,也初始化Client端的自己的信使对象,并且通过sendMessage()方法发送消息给Server端,表示可以开始下载了。

3.3.5 Client端使用获取到的rMessenger来发送消息给Server端,同时将Client端的信使封装到消息中,一并发送给Server端。

private void sendMessage() {      Message msg = Message.obtain(null, MessengerService.TEST);//MessengerService.TEST=0      msg.replyTo = mMessenger;      try {          rMessenger.send(msg);      } catch (RemoteException e) {          e.printStackTrace();      }  }  

这里的MessengerService.TEST為Server端里的一个静态常量。Msg.replyTo=mMessenger;表示发送给Server端的信息里携带Client端的信使。

3.3.6 Server端获取Client端发送的消息并得到Client端的信使对象。

在接收到Client端的信息之后,Server端开啟timer模拟下载,并接收Client端的信使对象。

private Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);          switch (msg.what) {          case TEST:              Log.e(TAG, "Get Message from MainActivity.");              cMessenger = msg.replyTo;//get the messenger of client              mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);              break;          default:              break;          }      }  };  

3.3.7 Server端向Client端发送数据。

直接使用接收到的Client端的信使对象来发送当前下载进度给Client端。

class MyTimerTask extends TimerTask {      @Override      public void run() {          if (i == 100) {              i = 0;          }          try {              //send the message to the client          Message message = Message.obtain(null, MessengerService.TEST,i, 0);              cMessenger.send(message);          } catch (RemoteException e) {                  e.printStackTrace();          }              i++;      }  }  

3.3.8 Client端接收来自Server端的数据。

Client端的接收和Server端的接收狠类似。接收到Server端传过来的数据之后进行介面更新,以及下载进度更新。

private Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);          switch (msg.what) {          case MessengerService.TEST:              Log.e(TAG, "Get Message From MessengerService. i= "+msg.arg1);              int curLoad = msg.arg1;              mTextView.setText(curLoad+"%");              mProgressBar.setProgress(curLoad);              break;          default:              break;          }      }  };  

以下是AndroidManifest.xml文件:

这里在Service的註册中加入了过滤动作,只有相匹配的action才能启动相应的Service。

<?xml version="1.0" encoding="utf-8"?>  <manifest xmlns:android="http://schemas.android.com/apk/res/android"        package="com.seven.messengerservicedemo"        android:versionCode="1"        android:versionName="1.0">      <uses-sdk android:minSdkVersion="10" />      <application android:icon="@drawable/icon" android:label="@string/app_name">          <activity android:name=".MainActivity"                    android:label="@string/app_name">              <intent-filter>                  <action android:name="android.intent.action.MAIN" />                  <category android:name="android.intent.category.LAUNCHER" />              </intent-filter>          </activity>      <service android:name="MessengerService">          <intent-filter>      <action ndroid:name="com.seven.messagerservice.MessengerService" />          </intent-filter>      </service>  </application>  </manifest>

3.4 小结

通过Messenger来实现Activity和Service的交互,稍微深入一点我们就可以知道,其实Messenger也是通过AIDL来实现的。对於前两种实现方式,Messenger方式总体上来讲也是比较容易理解的,这就和平时使用Handler和Thread通信一个道理。

自定义接口交互

何谓自定义接口呢,其实就是我们自己通过接口的实现来达到Activity与Service交互的目的,我们通过在Activity和Service之间架设一座桥樑,从而达到数据交互的目的,而这种实现方式和AIDL非常类似(后文会说到)。

4.1 实现原理
自定义一个接口,该接口中有一个获取当前下载进度的空方法。Server端用一个类继承自Binder并实现该接口,覆写了其中获取当前下载进度的方法。Client端通过ServiceConnection获取到该类的对象,从而能够使用该获取当前下载进度的方法,最终实现实时交互。

4.2 实现步骤

4.2.1 新建一个Interface,并在其中创建一个用于获取当前下载进度的的空方法getCurrentLoad()。

package com.seven.servicetestdemo;  public interface ICountService {      public int getCurrentLoad();  }  

4.2.2 新建Server端DownService实现ICountService并在其中通过一个内部类ServiceBinder继承自Binder并实现ICoutService接口。

public class DownLoadService extends Service implements ICountService{  private ServiceBinder serviceBinder = new ServiceBinder();    public class ServiceBinder extends Binder implements ICountService{      @Override      public int getCurrentLoad() {          Log.i(TAG, "ServiceBinder getCurrentLoad()... i=:"+i);          return i;      }     }  @Override  public int getCurrentLoad() {      return 0;  }  } 

在Server端中,实现获取下载进度的空方法getCurrentLoad();这是Eclipse自动生成的,重点不在这裡。我们需要在ServiceBinder类中覆写getCurrentLoad()方法,这裡我们返回当前的下载进度i。

4.2.3 Client端使用bindService()绑定Server端。

if (startSerBtn == v) {      Log.i(TAG, "Start Button Clicked.");      bindService(intent, serConn, BIND_AUTO_CREATE);      timer.schedule(new MyTimerTask(), 1000, TIME * 1000);//这里一定要延迟一下再开始获取数据,不然会报空指针异常  }  

在Client端绑定Server端的同时,延迟1s开始获取下载进度。其中的intent = new Intent(“com.seven.test”) com.seven.test该字符串要与在AndroidManifest.xml中申明的一致。

4.2.4 Server端返回binder对象。

@Override  public IBinder onBind(Intent intent) {      Log.i(TAG, "DownLoadService.onBind()...");      return serviceBinder;  }  

这里的serviceBinder因为继承了Binder因此也是Binder对象。

4.2.5 Client端通过ServiceConnection来获取Server端的binder对象。

private ServiceConnection serConn = new ServiceConnection() {  @Override      public void onServiceDisconnected(ComponentName name) {          iCountService = null;      }             @Override      public void onServiceConnected(ComponentName name, IBinder service) {          Log.i(TAG, "onServiceConnected()...");          iCountService = (ICountService)service;      }  };  

获取的过程是在bindService()过程中完成的,这里的iCountService是接口ICountService的对象,在这里得到实例化。

4.2.6 在绑定完成之后,Server端会开启下载,在实际情况中Server端会开启独立线程用于下载,这里用i++来代替。

@Override  public void onCreate() {      super.onCreate();      Log.i(TAG, "DownLoadService.onCreate()...");      timer = new Timer();      timer.schedule(new MyTimerTask(), 0, TIME*1000);  }  class MyTimerTask extends TimerTask{      @Override      public void run() {          if(100==i){              i=0;          }          i++;      }  }  

bindService()方法执行之后会调用DownLoadService中的onCreate()方法,在其onCreate()方法中开启timer使得i++。

4.2.7 Server端已经开启了下载,那么Client端需要及时获取下载进度并在主界面上更新。

Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);          Log.i(TAG, "handleMessage...");          int curLoad = iCountService.getCurrentLoad();          mProgressBar.setProgress(curLoad);          currentTv.setText(curLoad+"%");      }   };  class MyTimerTask extends TimerTask{      @Override      public void run() {          mHandler.sendMessage(mHandler.obtainMessage());      }  } 

Client端的Timer在bindService()完成之后1秒再开始获取下载进度,获取方法是直接通过int curLoad = iCountService.getCurrentLoad();这里的getCurrentLoad()方法是DownLoadService内部类ServiceBinder中的方法。Client端将获取到的下载进度更新到介面上并更新进度条。

4.3 小结

通过上面的例子可以知道,这种方法简单实用,扩展性强,但其也有一些缺点,比如需要延迟一些再开始获取Server端的数据,从而无法完全实现从零开始同步更新。综其所述,通过自定义接口实现Activity与Service交互的方法还是比较实用的。适用於同进程中通信,不能进行跨进程通信。
==》补充:其实不必要,是你将上面的那句话放错了位置,你按钮按钮的时候,iCountService并没有实例化,如不延迟就会空指针,你如果在iCountService实例化之后,在循环去获取进度就不会出现空指针了,所以将循环获取的代码timer.schedule(new MyTimerTask(), 1000, TIME * 1000);/放在onServiceConnected(ComponentName name, IBinder service) 就OK啦

AIDL交互

什么是AIDL?

AIDL是Android Interface Definition Language的首字母缩写, 也就是Android接口定义语言。提及AIDL就不得不说下Android的服务,Android 支持两种服务类型的服务即本地服务和远程服务。

    本地服务无法供在设备上运行的其他应用程序访问,也就是说只能该应用程序内部调用,比如某些应用程序中的下载类服务,这些服务只能由内部调用。而对于远程服务,除了可以由本应用程序调用,还可以允许其他应用程序访问。远程服务一般通过AIDL来实现,可以进行进程间通信,这种服务也就是远程服务。    本地服务与远程服务还是有一些重要的区别。具体来讲,如果服务完全只供同一进程中的组件使用(运行后台任务),客户端一边通过调用 Context.startService()来启动该服务。这种类型的服务为本地服务,它的一般用途是后台执行长耗时操作。而远程服务一般通过bindService()方法启动,主要为不同进程间通信。我们也将远程服务称为AIDL支持服务,因为客户端使用 AIDL 与服务通信。Android中对于远程服务有多种叫法:远程服务、AIDL服务、外部服务和RPC服务。

5.1 AIDL实现流程图

这里写图片描述

这属于代理/存根结构,通过这张AIDL的流程图,很容易发现Android实现IPC其实是在原来的C/S框架上加入了代理/存根结构。

比如,你到自动取款机上去取款。那么你就是客户(Client),取款机就是你的代理(Proxy);你不会在乎钱具体放在那里,你只想将你的钱从取款机中取出来。你同银行之间的操作完全是取款机代理实现。你的取款请求通过取款机传到另一边,即银行的服务器(Server)。它也没有必要知道你在哪儿取钱,它所关心的是你的身份和你取款多少。当它确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。取款机不是直接同服务器连接的,他们之间还有一个“存根(Stub)”,取款机与存根通信,服务器与存根通信,从某种意义上说存根就是服务器的代理。

5.3 实现原理
AIDL属于Android的IPC机制,常用于跨进程通信,主要实现原理基于底层Binder机制。

5.4 实现步骤
5.4.1 建立工程。按照图5.3和图5.4建立AIDLServer端以及AIDLClient端。在AIDLServer端中只有一个服务程序,没有主界面,其主要功能就是负责下载。AIDLClient端从AIDLServer端获取当前下载进度(注:AIDLServer端和AIDLClient端是不同的两个APK,在模拟本例的时候,需要先在模拟器上安装AIDLServer编译出来的APK,安装方法可以直接在模拟器上运行一次,可以通过adb install your.apk 来安装)。

这里写图片描述

AIDLServer端中新建了一个ICountService.aidl的文件,该文件内容如下:

package com.seven.aidlserver;  interface ICountService{      int getCount();  }  

aidl文件的书写规范如下:
(1). Android支持String和CharSequence(以及Java的基本数据类型);

(2). 如果需要在aidl中使用其它aidl接口类型,需要import,即使是在相同包结构下;

(3). Android允许传递实现Parcelable接口的类,需要import;

(4). Android支持集合接口类型List和Map,但是有一些限制,元素必须是基本型或者前面三种情况,不需要import集合接口类,但是需要对元素涉及到的类型import;

(5). 非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。
这里写图片描述

AIDLClient端需要将AIDLServer端的ICountService.aidl文件复製过去,这裡为了方便,新建了一个和Server端同名的包,并将ICountService.aidl放与其中。

5.4.2 我们在Server端建立好ICoutService.aidl文件之后,Eclipse会在/gen/com.seven.aidlserver/目录下自动生成ICountService.java文件。该文件由Eclipse自动生成,请勿随便修改,后文我们需引用到的内容如下:

public static com.seven.aidlserver.ICountService asInterface(android.os.IBinder obj) {      if ((obj == null)) {          return null;      }  android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);      if (((iin != null) && (iin instanceof com.seven.aidlserver.ICountService))) {          return ((com.seven.aidlserver.ICountService) iin);      }      return new com.seven.aidlserver.ICountService.Stub.Proxy(obj);  }  

5.4.3 在Server端新建一个内部类继承自ICountService.Stub并覆写其中的getCount()方法,以及实例化该类的一个对象serviceBinder。

private AIDLServerBinder serviceBinder = new AIDLServerBinder();  class AIDLServerBinder extends ICountService.Stub{      @Override      public int getCount() throws RemoteException {          return i;      }  }  

这里与前面提到的“通过接口实现交互”非常类似。

5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder对象。

@Override  public IBinder onBind(Intent intent) {      Log.i(TAG, "AIDLServer.onBind()...");      return serviceBinder;  }  

5.4.5 在Server端的onCreate()方法中,开启timer,模拟下载。在Client端通过bindService()绑定Server端的时候,会首先执行Server端的onCreate()方法。

@Override  public void onCreate() {      super.onCreate();      Log.i(TAG, "AIDLServer.onCreate()...");      mTimer = new Timer();      mTimer.schedule(new MyTimerTask(), 0,TIME * 1000);  }  class MyTimerTask extends TimerTask{      @Override      public void run() {          if(i==100){              i=0;          }          i++;      }  }  

5.4.6 Client端通过bindService()绑定Server端。

if(startBtn==v){      Log.i(TAG, "start button click.");      mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);      mTimer.schedule(new MyTimerTask(), 1000 ,TIME * 1000);  }  

这里的intent = new Intent(“com.seven.aidlserver”);这里跟Server端注册Service时过滤的要一致,也就是说只有发出相同的action才会启动该Service。同时开启了一个timer用于获取下载进度。

5.4.7 Client端通过ServiceConnection来获取Server端的binder对象。

private ServiceConnection serConn = new ServiceConnection() {             @Override      public void onServiceDisconnected(ComponentName name) {          iCountService = null;      }             @Override      public void onServiceConnected(ComponentName name, IBinder service) {          Log.i(TAG, "AIDLClient.onServiceConnected()...");          iCountService = ICountService.Stub.asInterface(service);      }  };

这里的iCountService对象实际上就是ICountService的对象在此实例化。

5.4.8 获取当前下载进度并更新到界面上。

Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);          try {              int count =  iCountService.getCount();              mTextView.setText(count+"%");              mProgressBar.setProgress(count);          } catch (RemoteException e) {              e.printStackTrace();          }      }  };  

通过更新介面上的进度条,可以狠容易的后去当前下载进度。因為AIDLServer端只是一个继承自Service的服务,因此就不贴出其AndroidManifest.xml文件了。

5.5 小结

AIDL在Android中是进程间通信常用的方式,可能使用较為复杂,但效率高,扩展性好。同时很多系统服务就是以这种方式完成与应用程序通信的。

还有一种AIDL方式,http://blog.csdn.net/goodlixueyong/article/details/50299963,两个作者都写的很好也有源码,下次多看看,我还没看懂这个方式,好晕~~~

服务的生命周期

BindService的生命周期

1、多个client可以绑定至同一个service,但该 service 的onBind() 方法只会在第一个 client 绑定至其的时候被调用,当其他 client 再次绑定到它的时候,并不会调用 onBind() 方法,而是直接返回第一次被调用时产生的那个 IBinder 对象。也就是说,在其生命周期内,onBind() 只会被调用一次。

2、Bind Service 不会在后台无限期的一直运行,而是当所有绑定至其的组件都调用了 unbindService() 进行解绑之后,系统就会将其停掉以回收资源。

3、 http://rainbow702.iteye.com/blog/1144521参考这个代码使用
http://www.aichengxu.com/view/2444117

onStartCommand()

请注意onStartCommand()方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行。onStartCommand()的返回值必须是以下常量之一:

START_NOT_STICKY  
如果系统在onStartCommand()返回后杀死了服务,则不会重建服务了,除非还存在未发送的intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。 
 
START_STICKY
如果系统在onStartCommand()返回后杀死了服务,则将重建服务并调用onStartCommand(),但不会再次送入上一个intent, 而是用null intent来调用onStartCommand() 。除非还有启动服务的intent未发送完,那么这些剩下的intent会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。 

START_REDELIVER_INTENT
如果系统在onStartCommand()返回后杀死了服务,则将重建服务并用上一个已送过的intent调用onStartCommand()。任何未发送完的intent也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。

两种启动Service的生命周期对比

这里写图片描述

关于停止Service,如果service是非绑定的,最终当任务完成时,为了节省系统资源,一定要停止service,可以通过stopSelf()来停止,也可以在其他组件中通过stopService()来停止,绑定的service可以通过onUnBind()来停止service。

1、 startService()

一旦在项目的任何位置调用了 Context的 startService()方法,相应的服务就会启动起来,并回调 onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到 stopService()或stopSelf()方法被调用。注意虽然每调用一次 startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次 startService()方法,只需调用一次 stopService()或 stopSelf()方法,服务就会停止下来了。

当调用了 startService()方法后,又去调用 stopService()方法,这时服务中的 onDestroy()
方法就会执行,表示服务已经销毁了。

2、bindService()

调用 Context 的 bindService()来获取一个服务的持久连接,这时就会回调服务中的 onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到 onBind()方法里返回的 IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。

当调用了 bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行。

注意:!!!
对一个服务既调用了 startService()方法,又调用了 bindService()方法的,根据 Android 系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用 stopService()和 unbindService()方法,onDestroy()方法才会执行。

!!!特别注意:
1、你应当知道在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService 解除绑定(尽管 Activity 被 finish 的时候绑定会自动解除,并且Service会自动停止);

2、你应当注意 使用 startService 启动服务之后,一定要使用 stopService停止服务,不管你是否使用bindService;

3、同时使用 startService 与 bindService 要注意到,Service 的终止,需要unbindService与stopService同时调用,才能终止 Service,不管 startService 与 bindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService 之后服务才会停止,如果先调用 stopService 此时服务也不会终止,而再调用 unbindService 或者 之前调用 bindService 的 Context 不存在了(如Activity 被 finish 的时候)之后服务才会自动停止;

4、当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。

前台服务

服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服
务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。比如:音乐播放器,墨迹天气等。

创建

public class MyService extends Service {    ……    @Override    public void onCreate() {        super.onCreate();        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)            .setSmallIcon(R.drawable.notification_icon)            .setContentTitle("My notification")            .setContentText("Hello World!");        Notification notification = mBuilder.build();        Intent notificationIntent = new Intent(this, MainActivity.class);        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0);        startForeground(1, notification);        Log.d("MyService", "onCreate executed");    }......}

和通知的时候的区别:不再使用notificationManager来显示通知,而是调用了 startForeground()方法。这个方法接收两个参数,第一个参数是通知的 id,类似于 notify()方法的第一个参数,第二个参数则是构建出的 Notification对象。调用 startForeground()方法后就会让 MyService 变成一个前台服务,并在系统状态栏显示出来。

使用IntentService

参考:http://blog.csdn.net/yuzhiboyi/article/details/8702220

使用队列的方式将请求的Intent加入队列,然后开启一个worker thread(线程)来处理队列中的Intent,对于异步的startService请求,IntentService会处理完成一个之后再处理第二个,每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程,这里就给我们提供了一个思路,如果有耗时的操作与其在Service里面开启新线程还不如使用IntentService来处理耗时操作。

//建一个 MyIntentService 类继承自 IntentService,代码如下所示:public class MyIntentService extends IntentService {    public MyIntentService() {        super("MyIntentService"); // 调用父类的有参构造函数    }    @Override    protected void onHandleIntent(Intent intent) {    // 打印当前线程的id        Log.d("MyIntentService", "Thread id is " + Thread.currentThread().        getId());    }    @Override    public void onDestroy() {        super.onDestroy();        Log.d("MyIntentService", "onDestroy executed");    }}

子类中去实现 onHandleIntent()这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心 ANR 的问题,因为这个方法已经是在子线程中运行的了。另外根据 IntentService 的特性,这个服务在运行结束后应该是会自动停止的。

从源码可以分析出:
IntentService 实际上是Looper,Handler,Service 的集合体,他不仅有服务的功能,还有处理和循环消息的功能.

//启动Intent intentService = new Intent(this, MyIntentService.class);startService(intentService);

总结

1.Service不是一个单独的进程 ,它和应用程序在同一个进程中。
2.Service不是一个线程,所以我们应该避免在Service里面进行耗时的操作
关于第二点我想说下,不知道很多网上的文章都把耗时的操作直接放在Service的onStart方法中,而且没有强调这样会出现Application Not Responding!希望我的文章能帮大家认清这个误区(Service不是一个线程,不能直接处理耗时的操作)。

0 0