如何快速学会android的四大基础----Service篇
很多人都以为,只要学过一点java就可以马上写android应用了,这种想法的产生非常自然,因为现在网上有那么多的android开源实例,只要跟着来,也能够自己写一个播放器。但是,只有去写一个真正投入使用的android应用的人才会明白,一个完整的android应用的诞生并不是一件简单的事情,就算是一个播放器,考虑到在线音源,无损音源等等其他东西,也会变得很复杂,单是界面这块,就已经让人崩溃了:android有那么多的版本,要做到各个版本的界面都是一样的,需要一点功夫,如果单纯依赖基本组件,到时每个版本的界面都会不统一的。更可怕的是,横屏和竖屏时的界面怎么办?各个组件之间的布局呢?。。。界面是一件头疼的问题,如何保证这些界面能够流畅的切换呢?界面和Activity之间的数据交换呢?MVP模式就像MVC一样,也是需要我们去研究的东西。
一句话:应用可不仅仅只是能够用就行,还要有人用,用得好,并且是没有任何问题的在用。
再复杂的东西,也是由小东西一点点搭建起来。所以,打好基础非常重要。
android中有四大组件:Activity,Service,BroadcastReceiver和ContentProvider。其中Service级别上跟Activity其实差不多,只是Service只能在后台运行,适合那些不需要界面的操作,像是播放音乐或者监听动作等,因为它的名字就已经提示了:它就是一个服务。
Service同样也是运行在主线程中,所以不能用它来做耗时的请求或者动作,否则就会阻塞住主线程。如果真的要这么做,可以跟Activity一样的做法:新开一个线程。
Service根据启动方式分为两类:Started和Bound。其中,Started()是通过startService()来启动,主要用于程序内部使用的Service,而Bound是通过bindService()来启动,允许多个应用程序共享同一个Service。
Service的生命周期不像Activity那样复杂,当我们通过Context.startService()启动Service的时候,系统就会调用Service的onCreate(),接着就是onStartCommand(Intent, int, int),过去这个方法是onStart(),但现在onStart()已经不被鼓励使用了,onStartCommand()里面的代码就是我们Service所要执行的操作,接着就是onDestroy(),关闭Service。
我们也可以通过Context.bindService()来启动,系统同样会调用Service的onCreate(),接着并不是onStartCommand()而是onBind(),它会将多个客户端绑定到同一个服务中。如果我们想要停止Service,必须先对客户端解绑,也就是调用onUnbind(),然后就是onDestroy()。
这就是Service的大概生命周期。
既然启动Service有两种方式,那么我们应该选择哪一个呢?如果单单只是为了使用Service,两种方式都可以,但正如我们上面所看到的,Bound启动的Service可以允许多个应用程序绑定到Service,所以,如果该Service是多个程序共享的,必须使用Bound来启动Service。
我们还可以将Service声明为应用程序的私有Service,这样就可以阻止其他应用获取该服务。
就像Activity,当我们在应用程序中使用自定义的Service的时候,我们必须在manifest中声明该Service。就像这样:
<application> <service android:name=".MyService"/> </application>
就像Activity一样,我们可以定义Service的Intent Filters,这样其他应用程序就可以通过Intent来使用该Service。如果没有定义Intent Filters,默认下是私有的,无法访问,当然,我们也可以显示的指定该访问权限:android:exported=false。
使用Service的时候必须注意,Service是在主线程中运行的,所以任何阻塞或者耗时操作都必须在Service中新开一个Thread。
接下来我们就来创建一个简单的Service:MyService,用于在后台播放MP3。这是介绍Service时经常用到的例子。
public class MyService extends Service { MediaPlayer mediaPlayer = null; @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onCreate() { if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(this, uri); // uri 为要播放的歌曲的路径 super.onCreate(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { mediaPlayer.start(); return START_STICKY; } @Override public void onDestroy() { mediaPlayer.stop(); super.onDestroy(); } }
由于我们是通过Started启动Service,所以onBind()返回的是NULL。
其实,onStartCommand()中的代码是我们Service主要的操作,它会在每次Service运行的时候被调用,而onCreate()是当Service第一次被创建的时候才会被调用,当Service已经在运行的时候,不会调用该方法,可以将一些初始化的操作放在这里。
定义好Service后,我们就可以在Activity中启动该Service:
Intent intent = new Intent(this, MyService.class); startService(intent);
这就像是在Activity中跳转到另一个Activity一样。
看上去Service就好像是在Activity中新开一个Thread一样,事实上,这两者也是挺像的,那么,我们是如何确定我们需要的是Thread还是Service?
如果是当用户使用我们的应用程序,像是点击按钮的时候,才执行一个在主线程外面的操作,我们需要的就是一个Thread而不是Service。
还是我们上面的例子,如果我们想要播放音乐是在我们应用程序运行的时候,那么我们可以再onCreate()中新建一个Thread,然后在onStartCommand()中开始该Thread,接着是在onStop()中停止该Thread。这样的做法非常自然,是一般的新手都会想到的,但是android鼓励我们使用AsyncTask或者HandlerThread来处理这个过程。关于这个话题,我们还是放在其他文章里讲吧,毕竟这是一个不小的话题。
当我们开启了一个Service,我们就有义务去决定什么时候关闭该Service。可以通过stopSelf()让Service自己关闭自己或者调用stopService()来关闭该Service。
当内存不够的时候,系统也会自动关闭Service。这时系统是如何选择哪些Service应该被关闭呢?如果一个Service被绑定到Activity并且得到用户的焦点,那么它就很少可能会被关闭,但如果它是被声明运行在前台,它就永远也不可能会被关闭。
知道这个事实对我们有什么用呢?当然是考虑如果内存足够的时候我们如何重启Service。
解决这个问题的关键就在于onStartCommand()的返回值。
onStartCommand()指定要求返回一个整数值,这个整数值描述的就是系统应该以何种方式来重启该Service,一共有下面三种方式:
1.START_NOT_STICKY:系统在关闭Service后,不需要重新创建该Service,除非我们传递Intent要求创建Service。这是避免不必要的Service运行的最安全的方式,因为我们可以自己指定什么时候重新启动该Service。
2.START_STICKY:要求重新创建Service并且系统会通过一个空的Intent来调用onStartCommand()除非我们传递Intent。我们看到,上面的例子就使用了该返回值,这样当音乐播放被停止后,重新启动的时候就会重新播放音乐。
3.START_REDELIVER_INTENT:这种方式会通过关闭前的Intent来调用onStartCommand()。它非常适合像是下载文件这类的操作,这样当重新启动的时候,就会继续之前的下载而不会丢失之前的下载进度。
关闭Service有两种方式:stopSelf()和stopService(),它们是有区别的,而且区别非常大。如果我们在一个应用程序中开启了多个Service,那么我们就不能贸然的使用stopService()来关闭Service,因为前一个Service的关闭可能会影响到后面Service的使用。这时我们就需要使用stopSelf(int startId)来决定关闭哪个Service。
关闭Service是非常重要的,这对于减少内存消耗和电量消耗来说,都是一件好事。
Service是在后台运行的,但是有时候我们需要向用户发送一些提示,像是下载完成之类的,这时就可以通过Toast或者Status Bar了。
我们上面提到,Service可以运行在前台,这点就非常奇怪了:Service不是后台运行的吗?其实不然,有时候我们也想要清楚的知道Service的进度,像是下载文件的进度或者歌曲的播放进度。这种Service一般都会在android手机上显示"正在进行的"的提示框里可以看到(就是我们手机上可以拉下来的那个框)。
这种Service一般都是通过Status Bar来提示进度,只有Service完成工作后才会消失:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text), System.currentTimeMillis()); Intent notificationIntent = new Intent(this, MyService.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent); startForeground(ONGOING_NOTIFICATION_ID, notification);
关键的方法就是startForeground(),它需要两个参数:唯一标识notification的id(不能为0)以及Status Bar的Notification。
当然,我们可以通过stopForeground()方法来停止Service在前台的显示,该方法并不会停止该Service。
要想使用Started Service,我们除了继承自Service之外,还可以继承自另一个类:IntentService。
IntentService是Service的子类。我们一般都是一个Intent就开启一个Service,但是IntentService是专门有一个线程来处理所有的开启请求,每次处理一个。这是我们需要在应用程序中开启多个Service但又想避免多线程的最佳选择。
public class MyService extends IntentService { MediaPlayer mediaPlayer = null; public MyService() { super("MyService"); } @Override protected void onHandleIntent(Intent intent) { if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(this, uri); }// uri 为要播放的歌曲的路径 mediaPlayer.start(); } }
和继承自Service不一样,我们需要一个构造器,该构造器的主要作用就是为工作线程命名。
我们仅需要覆写onHandleIntent()一个方法,因为该方法会为我们处理所有的Intent,并且会在处理完毕后关闭该Service。
IntentService就像是一个封装好的Service,方便我们处理多个Intent的情况,但如果我们想要覆写其他方法,也是可以的,但要确保每个方法最后都有调用super的实现。
接下来我们要讲的就是Bound Service。
要想创建Bound Service,我们就必须定义一个IBinder,它用于说明客户端是如何和服务通信的。Bound Service是一个非常大的话题,因为它涉及到本地服务还有远程服务。我们先从简单的本地服务开始:
public class LocalService extends Service { // Binder given to clients private final IBinder mBinder = new LocalBinder(); // Random number generator private final Random mGenerator = new Random(); /** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { LocalService getService() { // Return this instance of LocalService so clients can call public methods return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } /** method for clients */ public int getRandomNumber() { return mGenerator.nextInt(100); } }
这个例子非常简单,就是为每个绑定到该服务的客户产生一个0-100的随机数。
我们首先必须提供一个IBinder的实现类,该类返回的是我们Service的一个实例,这是为了方便客户调用Service的公共方法,接着我们在onBind()方法中返回这个IBinder的实现类。
这种做法适合Service只在应用程序内部共享,我们可以这样使用这个Service:
public class BindingActivity extends Activity { LocalService mService; boolean mBound = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } /** Called when a button is clicked (the button in the layout file attaches to * this method with the android:onClick attribute) */ public void onButtonClick(View v) { if (mBound) { // Call a method from the LocalService. // However, if this call were something that might hang, then this request should // occur in a separate thread to avoid slowing down the activity performance. int num = mService.getRandomNumber(); Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); } } /** Defines callbacks for service binding, passed to bindService() */ private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // We've bound to LocalService, cast the IBinder and get LocalService instance LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } }; }
我们通过bindService()将Activity和Service绑定到一起,接着必须定义一个ServiceConnection,为的就是通过绑定的IBinder的getService()方法来获得Service,然后我们再调用Service的getRandomNumber()。使用完后,我们需要通过unbindService()来解除绑定。
这里充分利用了回调的价值。
这种方式仅仅适合客户和服务都在同一个应用程序和一个进程内,像是音乐程序的后台播放。
结合我们上面有关Started Service的讨论,我们知道,可以用两种方式来开启服务,StartedService完全可以变成Bound Service,这样我们就不用显式的关闭服务,当没有任何客户和该Service绑定的时候,系统会自动的关闭该Service。但如果我们在一个Started Service中也同样使用了onBind()呢?像是这样的情况:我们在一个音乐播放程序中利用Started Service开启音乐播放,然后用户离开程序后,音乐会在后台播放,当用户重新返回到程序中时,Activity可以绑定到Service上以便对音乐进行控制。我们可以使用onRebind()方法来做到这点。
但我们有时候需要和远程的进程进行通信,这时就需要使用Messenger,这是为了实现进程间通信但又不想使用AIDL的唯一方式。
我们还是先来个简单例子:
public class MessengerService extends Service { /** Command to the service to display a message */ static final int MSG_SAY_HELLO = 1; /** * Handler of incoming messages from clients. */ class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SAY_HELLO: Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ final Messenger mMessenger = new Messenger(new IncomingHandler()); /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onBind(Intent intent) { Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); return mMessenger.getBinder(); } }
可以看到,我们必须先在Service里面实现一个Handler,然后将这个Handler传递给Messenger,然后在onBind()中返回的是该Messenger的getBinder()的返回值。
public class ActivityMessenger extends Activity { /** Messenger for communicating with the service. */ Messenger mService = null; /** Flag indicating whether we have called bind on the service. */ boolean mBound; /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the object we can use to // interact with the service. We are communicating with the // service using a Messenger, so here we get a client-side // representation of that from the raw IBinder object. mService = new Messenger(service); mBound = true; } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; mBound = false; } }; public void sayHello(View v) { if (!mBound) return; // Create and send a message to the service, using a supported 'what' value Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to the service bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } }
Activity所要做的就是闯将一个Messenger,该Messenger拥有Service返回的IBinder,接着就是发送消息给Service,然后Service中的Handler根据消息进行处理。我们可以看到,Messenger之所以能够实现进程间的通信,靠的还是Handler。
如果是为实现进程间的通信,我们可以使用Android Interface Definition Language(AIDL)。这个是个大话题,它体现的是一种编程思想,就是将对象分解成操作系统能够理解和执行的基元,像是上面的Messenger,实际上也是建立在AIDL的基础上。Messenger实际上是在一个单独的线程里创建客户的请求队列,所以Service每次只能接收到一个请求。但是,如果我们想要我们的Service能够处理多个请求,那么我们可以直接使用AIDL,也就是说,我们的Service就会成为多线程,相应的线程安全问题也随之而来。这真的是一件麻烦事!
要使用AIDL,我们必须建立.aidl文件,然后在这个文件中定义我们的接口,Android SDK工具会使用该文件去创建一个abstract class去实现这个接口,并且处理IPC,我们唯一要做的就是继承这个abstract class。
这就是远程代理模式的运用。
关于AIDL,这里不会涉及到太多东西,因为大部分的程序是不鼓励使用的,毕竟它是多线程的,很容易出现问题,而且如果我们的程序出现多线程,基本上可以认定,我们的程序设计是有问题的。
Service的基本内容大概就是这样,具体的设计问题得到具体的情景才能知道,但万变不离其宗,只要我们知道基础,就算一时间解决不了复杂的问题,也可以有个思绪。