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

        

        

  • 相关阅读:
    python语法小应用---列表和元组
    初识numpy
    PageRank算法
    安装最新版的2016版Pycharm后,激活码
    Python中的一些小语法
    卷积神经网络Convolutional Neural Networks
    如何选取一个神经网络中的超参数hyper-parameters
    初识神经网络NeuralNetworks
    VMware环境和Window环境进行网络连接的问题
    C语言标准
  • 原文地址:https://www.cnblogs.com/wenjiang/p/3231986.html
Copyright © 2011-2022 走看看