第一行代码读书笔记 10 -- 探究服务(中)

本篇文章主要介绍以下几个知识点:

  • 服务的基本用法
  • 服务的生命周期
  • 服务的其他用法
图片来源于网络

10.2 服务的基本用法

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。
  服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。
  服务并不会自动开启线程,所有代码默认运行在主线程中。

10.2.1 定义一个服务

在项目中定义一个服务:在 Android Studio 中右击 com.wonderful.myfirstcode.chapter10.service包(你项目所在的包名)→New→Service→Service,会弹出如下窗口:

创建服务的窗口

上面将服务命名为 MyService,Exported 表示是否允许除了当前程序之外的其他程序访问这个服务,Enabled 表示是否启用这个服务。完成创建后的 MyService 如下:

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

可以看到,onBind() 方法是 Service 中唯一的一个抽象方法,必须在子类中实现。在服务中添加处理事情的逻辑还要重写 Service 中的另外一些方法,如下:

public class MyService extends Service {

    . . .

    /**
     * 在服务创建时调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("------MyService------", "onCreate: ");
    }

    /**
     * 在每次服务启动时调用
     * 若服务一旦启动就立刻执行某个动作,可以将逻辑写在此方法中
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("------MyService------", "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 在服务销毁时调用,回收不再使用的资源
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("------MyService------", "onDestroy: ");
    }
}

另外需注意的是,每一个服务都需要在 AndroidManifest.xml 文件中进行注册才能生效(安卓四大组件的共有特点)。当然,刚才创建服务时 AS 已经自动帮我们注册好了:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wonderful.myfirstcode">

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        . . .

        <service
            android:name=".chapter10.service.MyService"
            android:enabled="true"
            android:exported="true">
        </service>
    </application>

</manifest>

以上,就将一个服务定义好了。

10.2.2 启动和停止服务

启动和停止服务的方法主要是借助 Intent 来实现的。下面就在项目中尝试去启动和停止服务。

在布局中添加两个按钮,分别用于启动和停止服务:

public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_service);

        Button start_service = (Button) findViewById(R.id.start_service);
        Button stop_service = (Button) findViewById(R.id.stop_service);
        start_service.setOnClickListener(this);
        stop_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:
                Intent startIntent = new Intent(this,MyService.class);
                startService(startIntent);// 启动服务
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this,MyService.class);
                stopService(stopIntent);// 停止服务
                break;
            default:
                break;
        }
    }
}

上述代码,启动服务 startService() 和停止服务 stopService() 方法都是定义在 Context 类中的,在活动里可以直接调用。当然,停止服务也可以在 MyService 的任何一个位置调用 stopSelf() 方法,让服务自己停止下来。

运行程序,点击启动服务,打印日志如下:

启动服务时打印日志

点击停止服务,打印日志如下:

停止服务时打印日志

值得注意的是,onCreate() 在服务第一次创建时调用,onStartCommand() 在每次启动服务时都会调用,上面第一次点击启动服务时两个方法都会执行,之后再点击启动服务按钮就只有 onStartCommant() 方法执行了。

10.2.3 活动和服务进行通信

上面一节中,虽然服务在活动里启动,但启动之后活动与服务就没什么关系了。若要在活动中指定服务做什么,就要借助服务里面的 onBind() 方法了。

下面举个例子,若在 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理,如下:

public class MyService extends Service {
       
    private DownloadBinder mBinder = new DownloadBinder();
    
    class DownloadBinder extends Binder{
        // 模拟开始下载方法
        public void startDownload(){
            Log.d("------MyService------", "startDownload: ");
        }
        // 模拟查看下载进度方法
        public int getProgress(){
            Log.d("------MyService------", "getProgress: ");
            return 0;
        }
    }

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

    . . .
}

接着在布局中添加两个按钮,用于绑定服务和取消绑定服务:

public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{

    private MyService.DownloadBinder downloadBinder;
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 服务绑定成功后调用
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 服务解除绑定后调用
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_service);

        Button bind_service = (Button) findViewById(R.id.bind_service);
        Button unbind_service = (Button) findViewById(R.id.unbind_service);
        bind_service.setOnClickListener(this);
        unbind_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){        
            case R.id.bind_service:
                Intent bindIntent = new Intent(this,MyService.class);
                // 绑定服务,三个参数:Intent对象、ServiceConnection实例、标志位
                // (BIND_AUTO_CREATE 表示活动和服务绑定后自动创建服务)
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                // 解绑服务
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

上述代码,首先创建了一个 ServiceConnection 的匿名类,里面重写两个方法,在 onServiceConnected() 中获取 DownloadBinder 的实例,接下来就根据具体场景来调用 DownloadBinder 中的任何公共方法。

运行程序,点击绑定服务,打印日志如下:

绑定服务时打印日志

点击解绑服务,打印日志如下:

解绑服务时打印日志

值得注意的是,任何一个服务在整个应用程序内都是通用的,即 MyService 可以和任何一个活动进行绑定,而且绑定完成后都可以获取到相同的 DownloadBinder 实例。

10.3 服务的生命周期

前面使用到的 onCreate()、onStartCommand()、onBind()、onDestroy() 等方法都是在服务的生命周期内可能回调的方法,具体如下图所示:

服务的生命周期

值得注意的是,当我们对一个服务既调用了 startService() 方法,又调用了 bindService() 方法时,要同时调用 stopService() 和 unbindService() 方法,onDestroy() 方法才会执行。

10.4 服务的更多技巧

10.4.1 使用前台服务

服务的优先级较低,当系统内存不足时,可能会回收正在后台运行的服务,若要避免被回收,可以考虑使用前台服务。

前台服务和普通服务的区别在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏可以看到更加详细的信息,类似于通知的效果。

创建一个前台服务如下:

public class MyService extends Service {
    . . .

   /**
     * 在服务创建时调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("------MyService------", "onCreate: ");
        Intent intent = new Intent(this,MyServiceActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("这是标题")
                .setContentText("这是内容")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        //让MyService变成一个前台服务,并在系统状态栏显示出来
        startForeground(1,notification);
    }

    . . .
}

前台服务的用法就这么简单,和创建通知的方法类似。运行程序,点击开启服务或绑定服务,效果如下:

前台服务的状态栏效果

10.4.2 使用 IntentService

之前提到过服务中的代码都是默认运行在主线程当中,若直接在服务里处理耗时操作,容易出现 ANR(Application Not Responding)的情况。

为避免上述情况,应该在服务的每个具体的方法里开启一个子线程,在子线程里处理耗时操作。因此一个比较标准的服务可以写成如下形式:

public class MyService extends Service {
    . . .

   /**
     * 在每次服务启动时调用
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 处理具体的逻辑        
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
}

服务开启后会一直处于运行状态,必须调用 stopService() 或者 stopSelf() 才能停止服务,所以要实现一个服务在执行完毕后自动停止,可以这样写:

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 处理具体的逻辑        
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
 }

当然,为了可以简单地创建一个异步地、会自动停止地服务,Android 专门提供了一个** IntentService **类。

下面介绍下 IntentService 类的用法,创建一个 MyIntentService 类继承自 IntentService,如下:

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");//调用父类的有参构造函数
    }

    /**
     * 此方法在子线程中运行,可以处理一些具体的逻辑,且不用担心 ANR 问题
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        // 打印当前线程的 id
        Log.d("MyIntentService", "onHandleIntent: 线程id是 "+ Thread.currentThread().getId());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService", "onDestroy: 服务停止");
    }
}

下面举个例子来证实下,在布局中添加个按钮用于启动 MyIntentService 这个服务,如下:

public class MyServiceActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_service);

        Button start_intent_service = (Button) findViewById(R.id.start_intent_service);
        start_intent_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_intent_service:
                // 打印主线程的 id
                Log.d("MyServiceActivity", "onClick: 主线程id:"+ Thread.currentThread().getId());
                Intent intentService = new Intent(this,MyIntentService.class);
                startService(intentService);
                break;
        }
    }
}

不要忘了在 AndroidManifest.xml 里注册服务(当然也可以用 AS 提供的快捷方式创建服务):

<service android:name=".chapter10.service.MyIntentService" />

运行程序,点击按钮,打印日志如下:

启动IntentService时打印日志

可以看到,MyIntentService 在运行完毕后自动停止了。

本篇文章介绍到这,下一小节进入服务的最佳实践。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 原文地址:Android Service完全解析,关于服务你所需知道的一切(上) 相信大多数朋友对Service这...
    AiPuff阅读 4,129评论 11 98
  • HandlerThread是一个Android 已封装好的轻量级异步类。HandlerThread本质上是一个线程...
    kjy_112233阅读 1,273评论 0 9
  • 今天的计划是第二次在线带读。 但是,小妞依然在清晨发烧了,这是连续第四天,我整个人处在晕乎乎的状态,我的计划是清晨...
    by_10阅读 281评论 0 2
  • 风 文/乌蒙骄子 北风咆哮着 像是要把秋天撕碎 广告牌终于找到自由 可以宣传到风的尽头 卷帘门在怒吼...
    乌蒙驕子阅读 241评论 0 1