Android四大组件完全解析(二)---Service

Service两大功能 :

  1. 当应用程序不与用户交互时,运行一些需要长时间运行的操作
  2. 为其他应用提供一些功能(提供能够跨进程调用的功能)

Service的配置:

service需要在所在应用的androidmanifest文件中进行配置:

<!-- name是service的包名路径-->
<!-- permission定义了开启该service时所需要的权限-->
<service android:name="com.android.server.backup.KeyValueBackupJob"
                 android:permission="android.permission.BIND_JOB_SERVICE" > </service>

Service的两种启动方式:

  1. startService : Context.startService()
  2. bindService:Context.bindService()

Service笔记: service运行在进程的主线程中,这也就是说,如果service需要做一些消耗CPU的操作(比如回放mps3)或者是阻塞的操作(请求网络),那么此时就要单独开一个线程来进行这些耗时或者占用cpu的操作。

Service详解

接下来涉及到的内容包括

  • #WhatIsAService
  • #ServiceLifecycle
  • #Permissions
  • #ProcessLifecycle
  • #LocalServiceSample
  • #RemoteMessengerServiceSample

WhatIsAService:什么是service?

在研究service是什么时,先来看看service不是什么: - service不是一个单独的进程。除非特别说明,否则service不会运行在他自己的进程中,而是运行在应用进程中。 - service不是一个线程。service也不是脱离主线程而运行的线程(避免anr:Application not responding)

service本身很简单,有两个主要的特征:

  1. 告知系统,应用有哪些事情想要在后台运行,(即使用户不直接与应用交互)。通过startService来让系统安排一个service,一直运行直到被明确stop.
  2. 把应用的一部分功能暴露给另一个应用。通过bindService绑定服务,该方法可以让service长时间维持已完成交互。

当service通过以上任何一种方式启动后,系统就会去初始化service并且调用service的onCreate方法,以及主线程中其他相关回调。这取决于service是否实现了一些相关的行为,比如开启一个线程来进行service所要做的工作。

Service本身很简单,可以借助service来实现一些简单或者复杂的交互。可以把service看成一个提供各自功能方法的Java本地对象,也可以是借助aidl实现远程调用。

ServiceLifeCycle:service的生命周期

有两种方式可以让service开启:

先来看一张Android源码提供的service的生命周期图

第一种:调用startService()方法开启服务。

由图可知,此时service 的生命周期为onCreate–>onStartCommand–>onDestroy。 system会调用service的onCreate方法(如果需要的话)和onStartCommand方法,其中onStartCommand方法中的参数由客户端提供。service从此开始一直运行直到调用stopService或者stopSelf停止。多次调用startService方法时会多次触发onStartCommand方法,但是一旦调用了stopService或者是stopSelf方法,service会被停止(不论调用了多少次startService方法),开发者可以调用stopSelf(int) 来当service的开启意图被处理后才去停止service 当服务开启后有两种主要的运行模式,这个运行模式取决于onStartCommand的返回值:

 public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    }

首先看一下决定返回值的boolean变量mStartCompatibility的取值

  public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
 .........

        mStartCompatibility = getApplicationInfo().targetSdkVersion
                < Build.VERSION_CODES.ECLAIR;
    }

Bluid.VERSION_CODES.ECLAIR取值是API5,对应的Android版本是2.0。也就是说Android2.0以前service的运行模式是START_STICKY_COMPATIBILITY,Android2.0以后service的运行模式为START_STICKY。这是Service.java中默认返回的值,但总共有四种取值,那么这四种模式都是什么,分别有什么区别呢?

  • START_STICKY_COMPATIBILITY(0):兼容2.0以下版本,在service被杀死之后,不保证service会再次调用onStartCommand。
  • START_STICKY(1):如果service在开启后(调用了onStartCommand方法)被杀死,则会保留service的开启状态,但不会保存开启service的intent意图。因为service处于started的状态,所以稍后系统会尝试重新创建re-create service,但此时调用onStartCommand方法时intent为null,所以这种情况下需要检查intent参数是否为null。例如,播放音乐:可以在任意时刻开始或者停止播放音乐。
  • START_NOT_STICKY(2):Service开始运行后如果被kill之后,service就不会处于started的状态,并且intent也不会被记录。所以service也就不会自动的重新创建re-create,当然如果调用了startService的话还是会重新创建的。该情况下onStartCommand中intent的值不为null。例如,闹钟–实现对数据的一个轮询,闹钟会在每过N分钟就会去开启一个service,当调用onStartCommand时会让alarm在N分钟后再次启动服务,以达到轮询的效果(即循环的开启服务)。(闹钟服务代码位于AlarmService.java中)
 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    ........
     return Service.START_NOT_STICKY;
    }
  • START_REDELIVER_INTENT(3):顾名思义,redeliver_intent,重新传一遍intent。当service被kill之后,会重新开启restarted,并且传入最后一次开启服务的intent。Intent会被一直保存,直到调用了stopself(int)终止了某个intent。在这种情况下onStartCommand中的intent不会为null,因为只有intent不为null的情况下被kill的service才会别restarted。 第二种:调用bindService绑定服务(来创建一个可以持久连接的服务)。 此时,service的生命周期为onCreate–>onBind–>onUnBind–>onDestroy 如果服务不处在running的状态,则会去调用onCreate方法,但是不会调用onStartCommand方法。Service会调用onBind方法返回一个 android.os.IBinder对象提供给客户端。客户端借助IBinder对象实现与service之间的通信。一旦service连接成功,则该service就会处于running的状态(无论客户端是否有Service的IBinder对象,他都会处于running的状态)。通常IBinder返回的是一个aidl中的对象。 一个Service可以同时保持开启和绑定连接两种形式。在这种情况下,service只要是被start或者是有一个多个连接存在(这些连接带有的标志为BIND_AUTO_CREATE、BIND_AUTO_CREATE),service就会保持running。 一旦以上所有情况都停了,service就会调用onDestroy方法并且终止。所有的清理工作(停止线程,注销广播接收器)都要在onDestroy时完成。 如下图所示

图中所述,

  • 前提条件:service调用了startService和onBind两种方式开启service
  • 当所有client与service解除绑定时才会触发onUnbind
  • 触发onUnbind之后去判断service是否自身调用了stopself或者是通过stopService停止了服务,即是否针对startService做了相应的结束
  • 如果既解除了绑定有停止了service,则service会被onDestroy
  • 若只是解除了绑定,则service不会调用destroy
  • 当client再次绑定时会判断service是否解绑成功
  • 如果没有解绑成功即onUnbind返回false则触发onRebind,否则触发onBind

Permissions

在Androidmanifest的service节点下可以添加一些属性对service的全局可用增加一些限制,比如:

<service android:name="。。。。。"
                 android:permission="android.permission.。。。。" > </service>

这种情况下,如果其他应用想要使用该service,需要在androidmanifest中声明用户权限user-permission,赋予它开启、停止、绑定服务的权限。

从Android2.3开始,在startService时可以设置intent的flag:

  • Intent#FLAG_GRANT_READ_URI_PERMISSION
  • Intent.FLAG_GRANT_READ_URI_PERMISSION
  • Intent#FLAG_GRANT_WRITE_URI_PERMISSION
  • Intent.FLAG_GRANT_WRITE_URI_PERMISSION 这些flag赋予service临时拥有访问Intent中特定uri的权限。临时拥有的意思是当sevice被stopSelf后或者是service被stopped之后,这种权限就不再保留。 举个例子:
 Intent intent = new Intent(Intent.ACTION_SEND);
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Uri.Builder b = new Uri.Builder();
                b.scheme("content");
                b.authority("com.example.android.apis.content.FileProvider");
                TypedValue tv = new TypedValue();
                getResources().getValue(R.drawable.jellies, tv, true);
                b.appendEncodedPath(Integer.toString(tv.assetCookie));
                b.appendEncodedPath(tv.string.toString());
                Uri uri = b.build();
                intent.setType("image/jpeg");
                intent.putExtra(Intent.EXTRA_STREAM, uri);
                intent.setClipData(ClipData.newUri(getContentResolver(), 
                "image",uri));
                startActivity(Intent.createChooser(intent, "Select 
                 share target"));

以上代码会赋予intent所指向的组件访问uri中type为image的数据。

ProcessLifecycle

Android系统会尽量将服务维持很长时间。什么样的服务会让系统这么做呢?开启的服务或者是与客户端绑定的服务

但是在系统在低内存的情况下时,不得不去杀死一些服务。接下来按照由高到低的优先级的顺序来说明(系统优先保持其运行的优先级)。

  • foreground process:如果service正在运行onCreate,onStartCommand或者是onDestroy()方法时主进程会被当成前台进程,保证其不被kill.
  • background service:当service被started之后,并且service的宿主进程相对于其他用户可见进程显得并没有那么重要但是比其他不可见的进程重要。通常只要很少一部分的进程处于用户可见的状态,也就是说service在内存充足的情况下service不会被杀死。但是对于一个处于background 的service,很容易被作为kill的对象,开发者需要对此做好应对的准备。尤其是长时间运行的后台service更容易被杀死
  • 但是如果service已经与客户端绑定,那么service的宿主进程(hosting process)永远要比绑定该service的客户端重要。也就是说,如果客户端对用户可见,则service即对用户可见。客户端重要性对于service重要性的影响可以通过一些标志位来调节(Context#BIND_ABOVE_CLIENT,Context#BIND_ALLOW_OOM_MANAGEMENT,Context#BIND_WAIVE_PRIORITY,Context#BIND_IMPORTANT,Context#BIND_ADJUST_WITH_ACTIVITY) 用法举例:
bindService(mBindIntent, conn,
                        Context.BIND_AUTO_CREATE | Context.BIND_ADJUST_WITH_ACTIVITY
                        | Context.BIND_WAIVE_PRIORITY)
  • 一个已经开启的service可以通过调用startForeground(int, Notification)来把service放在前台状态。来防止在低内存的情况下把service给杀死。

Sample

看一下官网给的例子

第一种情况:本地调用service

servcie最常见的用法就是作为应用程序的第二大组件。除非特殊声明,否则一个.apk中的所有组件都运行在同一个进程中。在同一个进程中时,调用service的client可以很容易就获取到service的Ibinder实体对像。 大致分为两步

  • 定义一个Service
  • 在andriodmenifest中配置service
  • 在client中绑定该service(同一个应用程序的client)

demo如下: 第一步,定义service

/**
*这是一个提供本地通信的service,即进程内通信
*/
public class LocalService extends Service {
    private NotificationManager mNM;

    // Unique Identification Number for the Notification.
    // We use it on Notification start, and to cancel it.
    private int NOTIFICATION = R.string.local_service_started;

    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     * 该service实现的是本地的通信,即不需要跨进程,所以只需提供一个Binder实体
     * 对象供其使用即可
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            return LocalService.this;
        }
    }

    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

        // Display a notification about us starting.  We put an icon 
        //in the status bar.
        //一旦通过start开启了service就会在状态栏进行通知
        showNotification();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("LocalService", "Received start id " + startId + ": " + intent);
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        // Cancel the persistent notification.
        mNM.cancel(NOTIFICATION);

        // Tell the user we stopped.
        Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    // This is the object that receives interactions from clients.  See
        // RemoteService for a more complete example.
    private final IBinder mBinder = new LocalBinder();

    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // In this sample, we'll use the same text for the ticker and 
        //the expanded notification
        CharSequence text = getText(R.string.local_service_started);

        // The PendingIntent to launch our activity if the user 
        //selects this notification
        //当点击该通知时启动activity
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, LocalServiceActivities.Controller.class), 0);

        // Set the info for the views that show in the notification panel.
        Notification notification = new Notification.Builder(this)
              // the status icon设置状态栏通知图标
             .setSmallIcon(R.drawable.stat_sample)  
              // the status text设置状态栏通知文本
             .setTicker(text)  
              // the time stamp设置时间戳
             .setWhen(System.currentTimeMillis())  
             // the label of the entry下拉时显示的title      
             .setContentTitle(getText(R.string.local_service_label))  
             // the contents of the entry下拉时显示的文本
             .setContentText(text) 
             // The intent to send when the entry is clicked点击时的动作
             .setContentIntent(contentIntent) 
             .build();

        // Send the notification.
        mNM.notify(NOTIFICATION, notification);
    }
}
//END_INCLUDE(service)

第二步,绑定服务或者是开启服务

 public static class Binding extends Activity {
        private boolean mIsBound;

// BEGIN_INCLUDE(bind)
        private LocalService mBoundService;

        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 service object we can use to
                // interact with the service.  Because we have bound to a explicit
                // service that we know is running in our own process, we can
                // cast its IBinder to a concrete class and directly access it.
//当绑定服务时会触发该方法
                mBoundService = ((LocalService.LocalBinder)service).getService();

                // Tell the user about this for our demo.
                Toast.makeText(Binding.this, R.string.local_service_connected,
                        Toast.LENGTH_SHORT).show();
            }

            public void onServiceDisconnected(ComponentName className) {
                // 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.
                mBoundService = null;
                Toast.makeText(Binding.this, R.string.local_service_disconnected,
                        Toast.LENGTH_SHORT).show();
            }
        };

        void doBindService() {
            // Establish a connection with the service.  We use an explicit
            // class name because we want a specific service implementation that
            // we know will be running in our own process (and thus won't be
            // supporting component replacement by other applications).
            bindService(new Intent(Binding.this, 
                    LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
        }

        void doUnbindService() {
            if (mIsBound) {
                // Detach our existing connection.
                unbindService(mConnection);
                mIsBound = false;
            }
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
            doUnbindService();
        }
// END_INCLUDE(bind)

        private OnClickListener mBindListener = new OnClickListener() {
            public void onClick(View v) {
                doBindService();
            }
        };

        private OnClickListener mUnbindListener = new OnClickListener() {
            public void onClick(View v) {
                doUnbindService();
            }
        };

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setContentView(R.layout.local_service_binding);

            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.bind);
            button.setOnClickListener(mBindListener);
            button = (Button)findViewById(R.id.unbind);
            button.setOnClickListener(mUnbindListener);
        }
    }

或者是通过start方式开启服务

 public static class Controller extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setContentView(R.layout.local_service_controller);

            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.start);
            button.setOnClickListener(mStartListener);
            button = (Button)findViewById(R.id.stop);
            button.setOnClickListener(mStopListener);
        }

        private OnClickListener mStartListener = new OnClickListener() {
            public void onClick(View v) {
                // Make sure the service is started.  It will continue running
                // until someone calls stopService().  The Intent we use to find
                // the service explicitly specifies our service component, because
                // we want it running in our own process and don't want other
                // applications to replace it.
                startService(new Intent(Controller.this,
                        LocalService.class));
            }
        };

        private OnClickListener mStopListener = new OnClickListener() {
            public void onClick(View v) {
                // Cancel a previous call to startService().  Note that the
                // service will not actually stop at this point if there are
                // still bound clients.
                stopService(new Intent(Controller.this,
                        LocalService.class));
            }
        };
    }

第二种情况:跨进程调用service:RemoteMessengerServiceSample

跨进程调用和进程内调用区别就在于如何去绑定一个服务,以及如何获取到binder对象。有了进程内调用的详细demo做参考,接下来会出一个简化的跨进程调用的demo

为了营造跨进程的条件,可以选择在同一台设备运行两个应用程序,或者是在一个应用程序中的清单配置文件中声明service的Android:proces属性在另一个进程中。 本例中,创建两个应用程序,首先是远程service的应用程序:

public class RemoteService extends Service {


    static class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 0:
                    Log.i("fang","客户端绑定啦~~");
                    Message message = Message.obtain();
                    try {
                        msg.replyTo.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    break;
            }
        }
    }

    final static Messenger messenger = new Messenger(new IncomingHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}

在另一个应用程序中调用

public class RemoteServiceActivities {

    /**
     * 绑定服务
     */
    public static class BindServiceActivity extends Activity implements View.OnClickListener{

        private boolean mIsBound ;
        private Messenger mService = null;
        private static class IncomingHandler extends Handler{
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.i("fang","接收到服务器端消息啦~~");
            }
        }

        private Messenger messenger = new Messenger(new IncomingHandler());
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //获取到binder
                mService = new Messenger(service);
                Message message = Message.obtain(null,0);
                //给service传递messenger用于信息返回
                message.replyTo = messenger;
                try {
                    mService.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                mService = null;
            }
        };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.bind);
            initView();
        }

        void initView(){
            TextView bind = (TextView) findViewById(R.id.bind);
            TextView unbind = (TextView) findViewById(R.id.unbind);
            setClickListener(bind,unbind);
        }

        void setClickListener(View...views){
            for (View view:views) {
                if (view != null){
                    view.setOnClickListener(this);
                }
            }
        }

        void doBindService(){
            Intent intent = new Intent("com.android.zrf.remoteservice");
            bindService(intent,connection,BIND_AUTO_CREATE);
            mIsBound = true;
        }

        void doUnbindService(){
            if (mIsBound){
                mIsBound = false;
                unbindService(connection);
            }
        }
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.bind:
                    doBindService();
                    break;
                case R.id.unbind:
                    doUnbindService();
                    break;
            }
        }
    }

    /**
     * 开启服务
     */
    public static class StartServiceActivity extends Activity implements View.OnClickListener{
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.start);
            initView();
        }

        void initView(){
            TextView start = (TextView) findViewById(R.id.start);
            TextView stop = (TextView) findViewById(R.id.stop);
            setClickListener(start,stop);
        }

        void setClickListener(View...views){
            for (View view:views) {
                if (view != null){
                    view.setOnClickListener(this);
                }
            }
        }
        void  startService(){
            Intent intent = new Intent(StartServiceActivity.this,LocalService.class);
            startService(intent);
        }

        void stopService(){
            Intent intent = new Intent(StartServiceActivity.this,LocalService.class);
            stopService(intent);
        }

        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.start:
                    startService();
                    break;
                case R.id.stop:
                    stopService();
                    break;
            }
        }
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏7号代码

Android应用界面开发——BroadcastReceiver(实现基于Service的音乐播放器)

BroadcastReceiver用于接收程序(开发者开发的程序和系统程序)发出的Broadcast Intent,程序启动BroadcastReceiver需...

1182
来自专栏Android小菜鸡

自定义Androidk全量更新组件

  自动更新功能对于一个APP来说是必备的功能,特别是对于未投放市场下载的APP,每次都让用户删掉原来的,再下载新的版本,肯定是不合适的。

922
来自专栏酷玩时刻

Android极速开发之设备管理器(DevicePolicyManager)

Android 2.2 SDK提供了一个可管理和操作设备的API叫DevicePolicyManager(这是设备管理的主类),使用这个API你可以接管手机的应...

1696
来自专栏mukekeheart的iOS之旅

Android基础总结(8)——服务

服务(Service)是Android中实现程序后台运行的解决方案,它非常适合用于去执行哪些不需要和用户交互而且还要长期运行的任务。服务的运行不依赖任何用户界...

3978
来自专栏非著名程序员

Android Service学习之本地服务

Service是在一段不定的时间运行在后台,不和用户交互应用组件。每个Service必须在manifest中 通过<service>来声明。可以通过contec...

1825
来自专栏潇涧技术专栏

Art of Android Development Reading Notes 8

《Android开发艺术探索》读书笔记 (8) 第8章 理解Window和WindowManager

861
来自专栏程序猿DD

Spring Cloud Zuul重试机制探秘

作者:李刚 原文:http://www.spring4all.com/article/208 简介 本文章对应spring cloud的版本为(Dalston....

1.8K10
来自专栏7号代码

Android应用界面开发——Service与IntentService(实现定时更换壁纸)

上面的Service只是重写了Service组件的onCreate()、onStartCommand()、onDestroy()、onBind()等方法,重写这...

1873
来自专栏KK的小酒馆

Service的跨进程开发Android开发高级进阶

Service的跨进程通信主要由两种Android提供的方法进行,一个是AIDL,通过创建一个AIDL文件来完成,另一个是利用Messenger,发送Messa...

1222
来自专栏刘望舒

探究RemoteViews的作用和原理

RmoteViews是一个能显示在其他进程的视图。同样也提供了一些基本的操作方法来修改视图的内容。

1301

扫码关注云+社区

领取腾讯云代金券