Android 认知与理解Service(一)

Android Service 基础知识点


  1. Service的简单概述
  2. Service的分类
  3. Service的生命周期

概念 什么是Service?

Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行

Service的分类

  • 按启动方式分类:通过Context.startService()或Context.bindService启动

主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService
当应用组件(如 Activity)通过调用 startService()
启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。

该方法启动的服务要进行通信。停止服务使用unbindService
当应用组件通过调用 bindService()
绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

  • 按寄存方式分类:本地服务,远程服务

本地服务(Local) 该服务依附在主进程上
服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被Kill后,服务便会终止。

远程服务(Remote)该服务是独立的进程
服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process=remote字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。例:一些提供系统服务的Service,这种Service是常驻的。

  • 按运行方式分类:前台服务,后台服务

前台服务 会在通知一栏显示 ONGOING 的 Notification
当服务被终止的时候,通知一栏的 Notification 也会消失,这样对于用户有一定的通知作用。前台Service在下拉通知栏有一条显示通知(主要做一些需要用户知道的事,但退出APP还能继续做,像音乐播放器类似的应用,在下拉栏有一些当前播放歌曲的信息和进行一些简单操作;有些下载功能放在Service里也会在下拉栏显示当前下载进度);前台Service优先级较高,不会由于系统内存不足而被回收。

后台服务 默认的服务即为后台服务,即不会在通知一栏显示 ONGOING 的 Notification。
当服务被终止的时候,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收

Service生命周期

service.jpg

按启动方式 startService和bindService 实例如下

1. startService 启动状态方法的调用顺序

自定义Service 加上对应的打印日志

public class MyService extends Service {

    private String TAG = "MyService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }
}

在MainActivity中调用如下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        findViewById(R.id.bind).setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, MyService.class);
            startService(intent);
        });
        
        findViewById(R.id.bind).setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, MyService.class);
            stopService(intent);
        });
    }
 }

多次点击start启动startService 最后点击stop停止stopService 打印日志如下:

2021-06-07 18:07:43.374 2219-2219 E/MyService: onCreate 
2021-06-07 18:07:43.375 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:53.286 2219-2219 E/MyService: onDestroy
2021-06-07 18:07:54.362 2219-2219 E/MyService: onCreate 
2021-06-07 18:07:54.362 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:55.715 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:56.035 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:56.215 2219-2219 E/MyService: onStartCommand
2021-06-07 18:07:57.306 2219-2219 E/MyService: onDestroy

总结如下

  1. 一个Service是可以多次启动的,但是onCreate只会回调一次
  2. 多次启动时 onStartCommand是启动一次就回调一次
  3. 开启服务可以多次,停止服务只能一次 stopService后Service.onDestroy

onStartCommand 是带有返回值的return super.onStartCommand(intent, flags, startId);,并且是我们开发者可以控制返回什么值。我们知道当系统内存是有限的,当系统内存资源不足,Service是会被销毁的,如果你在Service里做了什么重要事情,那被销毁显然是你不愿意看到的,所以要有一种方法让系统帮我们重启该服务,那要不要重启就由这个返回值 startId 决定了

  • START_STICKY:如果service进程被kill掉,系统会尝试重新创建Service,如果在此期间没有任何启动命令被传递到Service,那么参数intent将为null。
  • START_NOT_STICKY:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统不会自动重启该服务。
  • START_REDELIVER_INTENT:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统会自动重启该服务,并将intent的值传入。
  • START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

如果把返回值设置成START_STICKY和START_REDELIVER_INTENT,这样就可以处于不死状态了(这样做其实是不好的)还有一种情况是用户主动在设置界面把你的Service给杀死,但是这会回调onDestroy,你可以在这里发送广播重新启动。

onStartCommand()第二个输入参数flags正是代表此次onStartCommand()方法的启动方式,正常启动时,flags默认为0,被kill后重新启动,参数分为以下两种:

  • START_FLAG_RETRY:代表service被kill后重新启动,由于上次返回值为START_STICKY,所以参数 intent 为null
  • START_FLAG_REDELIVERY:代表service被kill后重新启动,由于上次返回值为START_REDELIVER_INTENT,所以带输入参数intent

服务必须在AndroidManifest.xml里注册

<service android:name=".Service.MyService" />

如果以这种方式启动服务,服务就会在后台无限期运行,即使启动的Activity被销毁了,也不会对Service有任何影响;除非手动调用stopService,服务才会停止

2.bindService启动状态方法的调用顺序

在MainActivity中代码示例如下:

public class MainActivity extends AppCompatActivity {

    private final String TAG ="MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnBind = findViewById(R.id.bind);
        Button btnUnBind = findViewById(R.id.unBind);

        btnBind.setOnClickListener(v -> {
            btnBind.setText("绑定成功");
            btnUnBind.setText("解绑服务");
            Intent intent = new Intent(this, MyService.class);
            bindService(intent,connection,BIND_AUTO_CREATE);
        });

        btnUnBind.setOnClickListener(v -> {
            btnBind.setText("绑定服务");
            btnUnBind.setText("解绑成功");
            unbindService(connection);
        });
    }

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG,"onServiceConnected");
            MyBind myBind = (MyBind) service;
            //这里就可以调用Service类的方法了
            myBind.startDownload();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG,"onServiceDisconnected");
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

Bind的代码示例如下:

public class MyBind extends Binder {

    private final String TAG = "MyBind";

    public void startDownload(){
        Log.e(TAG,"startDownload");
        downLoadMusic();
    }

    private void downLoadMusic(){
        Log.e(TAG,"downLoadMusic...");
    }
}

Service的代码示例如下:

public class MyService extends Service {

    private final String TAG = "MyService";

    private MyBind myBind;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate");
        myBind = new MyBind();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return myBind;
    }

    @Override
    public void onRebind(Intent intent) {
        Log.e(TAG,"onRebind");
        super.onRebind(intent);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG,"onUnbind");
        return super.onUnbind(intent);
    }
}

多次点击bindService绑定服务,点击unBindService接绑服务,打印日志如下:

2021-06-07 22:17:46.710 1625-1625 E/MyService: onCreate
2021-06-07 22:17:46.713 1625-1625 E/MyService: onBind
2021-06-07 22:17:46.725 1625-1625 E/MainActivity: onServiceConnected
2021-06-07 22:17:46.725 1625-1625 E/MyBind: startDownload
2021-06-07 22:17:46.725 1625-1625 E/MyBind: downLoadMusic...
2021-06-07 22:17:49.774 1625-1625 E/MyService: onUnbind
2021-06-07 22:17:49.775 1625-1625 E/MyService: onDestroy

总结如下

  1. 服务一绑定,会执行onCreate,再回调onBind,最后会走到ServiceConnection类的onServiceConnected中
  2. bindService只会绑定一次,不会多次绑定
  3. 如果想打印onRebind()的话,前提是onUnbind()方法返回值为true,但是如果使用默认的 super.onUnbind(intent)是不行的

注意点

  1. 不管是startService还是bindService,最后都要stopService和unBindService与之对应,要不然容易造成内存泄漏
  2. bindService后调用unBindService成功后,如果再调用unBindService将会报异常(java.lang.IllegalArgumentException: Service not registered),可以加一个boolean变量判断。
  3. 这里有一种特殊情况是先startService,此时回调onCreate->onStartCommand,然后再bindService,回调onBind->onServiceConnected;这时候要想销毁Service必须调用unBindService,再调用stopService。
  4. bindService成功后,Service就与当前Activity绑定了,它就跟随Activity生命周期走了,如果服务没有销毁,而与之绑定的Activity销毁了,那这个绑定的Service也会被销毁,但是Service里启动的子线程不会被销毁。
  5. 通过startService启动服务后,服务就与启动它的组件没有联系了,组件销毁了,服务还是继续运行;通过bindService启动服务,与之绑定的组件就可以控制Service的具体执行逻辑了

按Service运行方式分两类:前台服务和后台服务 实例如下

前台服务

MyService中的实例代码如下:

public class MyService extends Service {

    private static final int REQUEST_CODE = 1;

    private final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate");
        Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,REQUEST_CODE,intent,0);
        Notification.Builder builder = new Notification.Builder(getApplicationContext());
        builder.setContentIntent(pendingIntent)
                .setContentTitle("我是前台服务")
                .setContentText("这里是要显示的内容")
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher))
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentInfo("前台信息")
                .setWhen(System.currentTimeMillis());

        Notification build = builder.build();
        build.defaults = Notification.DEFAULT_SOUND;
        startForeground(123,build);

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return null;
    }
}

在MainActivity只需要开启服务就可以了,代码如下:

    Intent intent = new Intent(MainActivity.this, MyService.class);
    startService(intent);

总结

  1. 获取PendingIntent对象,至于PendIntent和Intent的区别,以及应用场景,以后会出文讲解一番
  2. 获取Notification.Builder利于Buidler模式,设置标题,内容,图标等
  3. Notification.DEFAULT_SOUND设置默认铃声
  4. startForeground(123,build)让Service变成前台Service,并在系统的状态栏显示出来

后台服务

后台服务默认的就是后台服务,这里就不在赘述了

后续将继续讲解IntentService的由来,以及应用的场景和对应的源码分析

结语:感谢各位大佬们的分享,浏览的用的东西多了,反而会很杂,所以需要梳理记录整理一下,如有错误的需要改进的地方,请留言评论指出,谢谢!

参考文章:

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

推荐阅读更多精彩内容