如何快速学会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()方法来做到这点。

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

 

    

    

posted @ 2013-08-06 10:35  文酱  阅读(6208)  评论(3编辑  收藏  举报