Android-四大组件之Service、

前言

  • 程序:为了完成特定任务,用某种语言编写的一组指令集合(一组静态代码)
  • 进程:运行中的程序,系统调度与资源分配的一个独立单位,操作系统会 为每个进程分配一段内存空间!程序的依次动态执行,经历代码的加载,执行, 执行完毕的完整过程!
  • 线程:比进程更小的执行单元,每个进程可能有多条线程,线程需要放在一个 进程中才能执行。
  • 线程由程序负责管理,而进程则由系统进行调度!
  • 进程和程序的区别:程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。
  • 多线程的理解:并行执行多个条指令,将CPU时间片按照调度算法分配给各个 线程,实际上是分时执行的,只是这个切换的时间很短,用户感觉到"同时"而已!

Service简介

  • Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。
    例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。
  • 既然Service不使用用户界面,那么它怎么知道应该什么时候开始执行什么操作呢?
    答案是:它可以与其他的引用组件形成一些联系,从而可以根据其传来的信息在合适的时候执行合适的操作。

Service相关方法详解

  • IBinder onOnbind(intent):该方法是Service都必须实现的方法,该方法会返回一个 IBinder对象,app通过该对象与Service组件进行通信!
  • onCreate():当Service第一次被创建后立即回调该方法,该方法在整个生命周期 中只会调用一次!
  • onStartCommand(intent,flag,startId):当客户端调用startService(Intent)方法时会回调,可多次调用StartService方法, 但不会再创建新的Service对象,而是继续复用前面产生的Service对象,但会继续回调onStartCommand()方法!
  • onDestory():当Service被关闭时会回调该方法,该方法只会回调一次!
public class TestService extends Service {

    private final String TAG = "TAG_TestService";

    /**
     * ╔═══════════════════════════════基本方法══════════════════════════════╗
     */

    //必须要实现的方法
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind方法被调用!");
        return null;
    }

    //Service被创建时调用
    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate方法被调用!");
        super.onCreate();
    }

    //Service被启动时调用
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand方法被调用!");
        return super.onStartCommand(intent, flags, startId);
    }

    //Service被关闭之前回调
    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy方法被调用!");
        super.onDestroy();
    }
}

    /**
     * ╔═══════════════════════════════Binder方法══════════════════════════════╗
     */

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind方法被调用!");
        return true;
    }

    @Override
    public void onRebind(Intent intent) {
        Log.i(TAG, "onRebind方法被调用!");
        super.onRebind(intent);
    }

Service的生命周期图

从上图生命周期,我们可以知道,Android中启动/使用Service的方式有两种:

  • 1)StartService()启动Service
  • 2)BindService()启动Service
  • PS:还有一种,就是启动Service后,绑定Service!

1)StartService启动Service

Intent intentStart = new Intent(ServiceActivity.this, StartService.class);
startService(intentStart);
  • 首次启动会创建一个Service实例,依次调用onCreate()和onStartCommand()方法,此时Service 进入运行状态。
    如果再次调用StartService启动Service,将不会再创建新的Service对象, 系统会直接复用前面创建的Service对象,调用它的onStartCommand()方法!
stopService(intentStart);
  • 这样的Service与它的调用者无必然的联系,就是说当调用者结束了自己的生命周期, 但是只要不调用StopService,那么Service还是会继续运行的!
  • 无论启动了多少次Service,只需调用一次StopService即可停掉Service

代码举例:

添加Service组件声明,在AndroidManifest.xml文件中声明一个Service组件,其代码如下:

        <service android:name=".service.StartService"/>
        Intent intentStart = new Intent(ServiceActivity.this, StartService.class);
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(intentStart);
            }
        });
        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intentStart);
            }
        });

2)BindService启动Service

bindService(Intent service,ServiceConnection conn,int flags);
  • 当首次使用bindService绑定一个Service时,系统会实例化一个Service实例,并调用其onCreate()和onBind()方法。然后调用者就可以通过IBinder和Service进行交互了。
    此后如果再次使用bindService绑定Service,系统不会创建新的Sevice实例,也不会再调用onBind()方法,只会直接把IBinder对象传递给其他后来增加的客户端!
  • 如果我们解除与服务的绑定,只需调用unbindService(),此时onUnbind和onDestory方法将会被调用,这是一个客户端的情况。
  • 假如是多个客户端绑定同一个Service的话,情况如下 当一个客户完成和service之间的互动后,它调用 unbindService() 方法来解除绑定。当所有的客户端都和service解除绑定后,系统会销毁service。(除非service也被startService()方法开启)
  • BindService模式下的Service是与调用者相互关联的,可以理解为 "一条绳子上的蚂蚱",要死一起死,在bindService后,一旦调用者销毁,那么Service也立即终止!

代码举例:

创建BindService.java继承自Service类,重写onCreate()方法、onBind()方法、onUnbind()方法、onDestroy()方法,实现本地通知栏显示,其代码如下:

public class BindService extends Service {
    //声明IBinder接口的一个接口变量mBinder
    public final IBinder mBinder = new LocalBinder();
    private NotificationManager mNM;
    private int NOTIFICATION = R.string.local_service_started;
    //LocalBinder是继承Binder的一个内部类
    public class LocalBinder extends Binder {
        public BindService getService() {
            return BindService.this;
        }
    }
    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        MLog.e(getClass().getName(), "onCreate");
        showNotification();
    }

    @Override
    public void onDestroy() {
        MLog.e(getClass().getName(), "onDestroy");
        mNM.cancel(NOTIFICATION);
        Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
    }
    @Override
    public IBinder onBind(Intent intent) {
        MLog.e(getClass().getName(), "onBind");
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        MLog.e(getClass().getName(), "onUnbind");
        return super.onUnbind(intent);
    }
    private void showNotification() {
        CharSequence text = getText(R.string.local_service_started);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, ServiceActivity.class), 0);
        Notification notification = new Notification.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setTicker(text)
                .setWhen(System.currentTimeMillis())
                .setContentTitle(getText(R.string.local_service_label))
                .setContentText(text)
                .setContentIntent(contentIntent)
                .build();
        mNM.notify(NOTIFICATION, notification);
        MLog.e(getClass().getName(), "通知栏已显现!");
    }
}

创建ServiceActivity.java

public class MainActivity extends AppCompatActivity {
    private BindService bindService;
    private boolean isBind = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service);
        findViewById(R.id.btn_bind).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isBind) {
                    Intent intentBind = new Intent(ServiceActivity.this, BindService.class);
                    bindService(intentBind, serviceConnection, Context.BIND_AUTO_CREATE);
                    isBind = true;
                }
            }
        });
        findViewById(R.id.btn_unbing).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isBind) {
                    isBind = false;
                    unbindService(serviceConnection);
                    bindService = null;
                }
            }
        });
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MLog.e(getClass().getName(), "onServiceConnected");
            bindService = ((BindService.LocalBinder) service).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            MLog.e(getClass().getName(), "onServiceDisconnected");
            bindService = null;
        }
    };
}

3)StartService启动Service后bindService绑定

如果Service已经由某个客户端通过StartService()启动,接下来由其他客户端 再调用bindService()绑定到该Service后调用unbindService()解除绑定最后在 调用bindService()绑定到Service的话,此时所触发的生命周期方法如下:

onCreate( )->onStartCommand( )->onBind( )->onUnbind( )->onRebind( )

PS:前提是:onUnbind()方法返回true!!!
这里或许有疑惑了,调用了unbindService后Service不是应该调用 onDistory()方法么?

  • 其实这是因为这个Service是由我们的StartService来启动的 ,所以你调用onUnbind()方法取消绑定,Service也是不会终止的!
  • 得出的结论: 假如我们使用bindService来绑定一个启动的Service,注意是已经启动的Service!!! 系统只是将Service的内部IBinder对象传递给Activity,并不会将Service的生命周期 与Activity绑定,因此调用unBindService( )方法取消绑定时,Service也不会被销毁!

注意事项

  • StartService使用的是同一个Service,因此onCreate()只执行一次,onStartCommand()会执行多次。
    使用BindService启动时,onCreate()与onBind()都只会调用一次。
  • 使用StartService启动时是单独开一个服务,与Activity没有任何关系.
    BindService方式启动时,Service会和Activity进行绑定,当对应的Activity销毁时,对应的Service也会销毁。

Activity与Service通信

通过Activity启动和停止Service,假如我们启动的是一个下载的后台Service。而我们想知道Service中下载任务的进度!
那么这肯定是需要Service 与Activity进行通信的,而他们之间交流的媒介就是Service中的onBind()方法! 返回一个我们自定义的Binder对象!

基本流程如下:

  • 1.自定义Service中,自定义一个Binder类,然后将需要暴露的方法都写到该类中!
  • 2.Service类中,实例化这个自定义Binder类,然后重写onBind()方法,将这个Binder对象返回!
  • 3.Activity类中实例化一个ServiceConnection对象,重写onServiceConnected()方法,然后 获取Binder对象,然后调用相关方法即可!
    private int count;
    private boolean quit;

    //定义onBinder方法所返回的对象
    private MyBinder mBinder = new MyBinder();

    public class MyBinder extends Binder {
        public int getCount() {
            return count;
        }
    }

    // 必须要实现的方法
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind方法被调用!");
        return mBinder;
    }

一个简单前台服务的实现

Service一般都是运行在后来的,但是Service的系统优先级 还是比较低的。
当系统内存不足的时候,就有可能回收正在后台运行的Service,
对于这种情况我们可以使用前台服务,从而让Service稍微没那么容易被系统杀死, 当然还是有可能被杀死的。
所谓的前台服务就是状态栏显示的Notification。

在自定义的Service类中,重写onCreate(),然后根据自己的需求定制Notification;
定制完毕后,调用startForeground(1,notification对象)即可! 核心代码如下:

    public void onCreate() {
        super.onCreate();
        Notification.Builder localBuilder = new Notification.Builder(this);
        localBuilder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0));
        localBuilder.setAutoCancel(false);
        localBuilder.setSmallIcon(R.mipmap.ic_cow_icon);
        localBuilder.setTicker("Foreground Service Start");
        localBuilder.setContentTitle("Socket服务端");
        localBuilder.setContentText("正在运行...");
        startForeground(1, localBuilder.getNotification());
    }

使用IntentService

Android的官方在介绍 Service有下面这样一段话:

  • 1、Service不是一个单独的进程,它和它的应用程序在同一个进程中。
  • 2、Service不是一个线程,这样就意味着我们应该避免在Service中进行耗时操作。

IntentService正好弥补了Service的不足。IntentService的特点:

  • IntentService会创建单独的worker线程来处理所有的Intent请求。
  • IntentService会创建单独的worker线程来处理onHandleIntent()方法实现的代码,因此开发者无须处理多线程问题。

代码举例:

创建TestIntentService.java继承自IntentService类,重写onHandleIntent()方法、创建一个无参构造函数,其代码如下:

public class TestIntentService extends IntentService {

    public TestIntentService() {
        super("TestIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        CZLog.e(getClass().getName(), "onHandleWork");
        for (int i = 0; i < 3; i++) {
            try {
                CZLog.e(getClass().getName(), "Number:开始"+i);
                Thread.sleep(10000);
                CZLog.e(getClass().getName(), "Number:结束"+i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        CZLog.e(getClass().getName(), "onDestroy");
    }
    
}

在AndroidManifest.xml文件中声明一个Service组件

<service android:name=".service.TestIntentService"/>

启动SccIntentService

startService(new Intent(MainActivity.this, TestIntentService.class));

普通Service直接执行20S的的耗时操作,会阻塞主线程,造成ANR(程序无响应)异常。

  • IntentService执行30S的耗时操作,不会阻塞主线程,更不会产生ANR。如上图开始18:01:12>18:01:42长达30S,正常运行未产生ANR。
  • IntentService还有个好处就是 用完即走。执行完onHandleIntent()方法里面的耗时操作后,自行调用onDestroy()方法,进行关闭。

其他

  • Service如果没有单独的开启一个进程,那么它是运行在主线程中的,即UI线程。
  • 所以在Service中不要进行一些耗时的操作。执行耗时的操作可能会引发ANR。
  • 引入Service的目的是为了执行一些后台的操作,例如下载播放音乐等。如果在Service中执行耗时的操作,应该在其中开启一个线程。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容