如何快速学会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()。

      如何快速学会android的四大基础----Service篇

这就是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()方法来做到这点。

       如何快速学会android的四大基础----Service篇
       这就是既是Started Service又是Bound Service的整个生命周期。

但我们有时候需要和远程的进程进行通信,这时就需要使用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的基本内容大概就是这样,具体的设计问题得到具体的情景才能知道,但万变不离其宗,只要我们知道基础,就算一时间解决不了复杂的问题,也可以有个思绪。

上一篇:BIOS设置和CMOS设置的区别与联系


下一篇:HTMLUnit web测试