美文网首页
Android面试一天一题(1 Day):service

Android面试一天一题(1 Day):service

作者: AiPuff | 来源:发表于2016-11-14 08:32 被阅读254次

    面试题:知道Service吗,它有几种启动方式?

    Service是一个专门在后台处理长时间任务的Android组件,它没有UI。它有两种启动方式,startService和bindService。

    你猜得没错,Bely紧接着问我:这两种启动方式的区别。

    startService只是启动Service,启动它的组件(如Activity)和Service并没有关联,只有当Service调用stopSelf或者其他组件调用stopService服务才会终止。

    bindService方法启动Service,其他组件可以通过回调获取Service的代理对象和Service交互,而这两方也进行了绑定,当启动方销毁时,Service也会自动进行unBind操作,当发现所有绑定都进行了unBind时才会销毁Service。

    这应该是比较关键的区别了,
    当然还有其他的区别,如两种调用对Service生命周期函数影响,面试官也可以就这个问题展开一下。


    当我遇到面试者知道怎么使用Service,也如多年前的我可以自如的答出startService和bindService的区别时,我一般会多问一句:
    Service的onCreate回调函数可以做耗时的操作吗?

    很多人都会说:可以。
    原形毕露,他前面的回答只是在面试前预习了一下面试题而已。如果知道Service的onCreate是在主线程(ActivityThread)中调用的,耗时操作会阻塞UI,我一般再接着问:
    如果需要做耗时的操作,你会怎么做?

    问题便这样展开了,一个人是否真正懂得原理会灵活运用,一下子便能看出来。 当面试者回答到线程和Handler方式时,我会再问一下对方:
    是否知道IntentService,在什么场景下使用IntentService?

    这也是面试官要看的点,真正的项目需要一个开发人员对某个问题有一定的深度,也需要对整个Android的知识点有一定的广度。深度代表这个人对问题认真对待有钻研的精神,广度代表这个人在面对同一个问题时,会更容易从多种可行的方案中选出最合适的一种。
    Service的实际项目中一直被很多人忽略,为什么我一再强调Service很重要,我们来看看,如果对Service完全无知会在工作中遇到什么问题。
    场景:如果一个应用要从网络上下载MP3文件,并在Activity上展示进度条,这个Activity要求是可以转屏的。那么在转屏时Actvitiy会重启,如何保证下载的进度条能正确展示进度呢?

    没有Service概念的人,一般想出来的方案如下:
    在转屏前将进度缓存,转屏后再读出来。
    使用android:configChanges设置,让转屏时Activity不销毁和重建。

    针对第1个方案,我会继续问他将进度值存在哪里? 转屏的过程中,我们知道Activity的重建算是比较耗时的,会可能会有几百毫秒以上,那么这时候下载线程仍然在工作,进度肯定和保存时的进度不一致了,如何处理这个问题呢?
    第2个方案,大家可以自己展开思考,实际的项目中可能会需要额外做一些事情来处理ContentView的横竖布局的问题。
    如果使用Service来解决这个问题,看似是比较完美的,不过就会涉及Activity(UI)和Service的交互问题,这个我们以后再讨论。
    小结
    当我们知道了Service的用途,心中有一个Service相关的概念时,针对实际的场景还是要做具体的分析再决定是否使用Service。因为Service仍然是在主线程中调用,还是要开线程才能处理长时间的工作,Service和UI的交互也让这个方式变得不那么简便。如果你只需要在当前界面去做一些耗时操作,界面退出或改变时,工作也要停止,那么这时直接使用Thread(或者AsyncTask, ThreadHandler)会更合适你。

    IntentService 示例与详解

    IntentService 是比较少使用的,如果你没听过也不意外,就像 HandlerThread 很多开发者没用过或没听过,不过我也仅仅是在demo中使用。Google 为方便开发者使用,提高开发效率,封装了 IntentService 和 HandlerThread。HandlerThread 继承自 Thread,内部封装了 Looper。那 IntentService 呢?
    IntentService 是继承自 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作,当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,执行完自动结束。
    示例代码
    实现的功能
    示例代码的功能是模仿下载在服务线程中每隔50ms计数一次直到计数为100停止,发送服务和线程运行状态的广播,Fragment中接收广播进行UI更新,如此。
    MyIntentService 全部代码

    public class MyIntentService extends IntentService {
    
        private boolean isRunning = true;
        private int count = 0;
        private LocalBroadcastManager mLocalBroadcastManager;
    
        public MyIntentService() {
            super("MyIntentService");
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
            sendServiceStatus("服务启动");
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            try {
                sendThreadStatus("线程启动", count);
                Thread.sleep(1_000);
                sendServiceStatus("服务运行中...");
    
                isRunning = true;
                count = 0;
                while (isRunning) {
                    count++;
                    if (count >= 100) {
                        isRunning = false;
                    }
                    Thread.sleep(50);
                    sendThreadStatus("线程运行中...", count);
                }
                sendThreadStatus("线程结束", count);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            sendServiceStatus("服务结束");
        }
    
        // 发送服务状态信息
        private void sendServiceStatus(String status) {
            Intent intent = new Intent(IntentServiceFragment.ACTION_TYPE_SERVICE);
            intent.putExtra("status", status);
            mLocalBroadcastManager.sendBroadcast(intent);
        }
    
        // 发送线程状态信息
        private void sendThreadStatus(String status, int progress) {
            Intent intent = new Intent(IntentServiceFragment.ACTION_TYPE_THREAD);
            intent.putExtra("status", status);
            intent.putExtra("progress", progress);
            mLocalBroadcastManager.sendBroadcast(intent);
        }
    }
    

    Fragment 中的广播相关代码

    public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case ACTION_TYPE_SERVICE:
                    tvServiceStatus.setText("服务状态:" + intent.getStringExtra("status"));
                    break;
                case ACTION_TYPE_THREAD:
                    int progress = intent.getIntExtra("progress", 0);
                    tvThreadStatus.setText("线程状态:" + intent.getStringExtra("status"));
                    progressBar.setProgress(progress);
                    tvProgress.setText(progress + "%");
                    break;
            }
        }
    }
    

    注册服务启动服务

    // 在 Manifest 中注册服务
    <service android:name=".service.MyIntentService"/>
    
    // 像启动 Service 那样启动 IntentService
    Intent startIntent = new Intent(getActivity(), MyIntentService.class);
    getActivity().startService(startIntent);
    

    具体参考GitHub上项目
    Gif图演示
    1、点击[启动 IntentService],服务运行,线程计数完成后,服务和线程都结束。

    图一

    2、点击[启动 IntentService],服务运行,线程计数完成前,点击[停止 IntentService],服务结束,线程计数完成后线程结束。


    图二

    3、点击两次[启动 IntentService],服务运行,第一次线程计数完成后,进行第二次线程计数,两次完成后,服务和线程都结束。


    图三

    4、点击两次[启动 IntentService],服务运行,在第一次线程计数完成前,点击[停止 IntentService],服务结束,第一次线程计数结束后不进行第二次计数。


    图四

    查看源码
    下面,从这几个功能点查看下源码:
    1、启动 IntentService 为什么不需要新建线程?

    // IntentService 源码中的 onCreate() 方法
    @Override
    public void onCreate() {
        super.onCreate();
        // HandlerThread 继承自 Thread,内部封装了 Looper,在这里新建线程并启动,所以启动 IntentService 不需要新建线程。
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
    
        // 获得工作线程的 Looper,并维护自己的工作队列。
        mServiceLooper = thread.getLooper();
        // mServiceHandler 是属于工作线程的。
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    
    private volatile ServiceHandler mServiceHandler;
    
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
    
        @Override
        public void handleMessage(Message msg) {
            // onHandleIntent 方法在工作线程中执行,执行完调用 stopSelf() 结束服务。
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    
    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);
    

    2、为什么不建议通过 bindService() 启动 IntentService?

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

    IntentService 源码中的 onBind() 默认返回 null;不适合 bindService() 启动服务,如果你执意要 bindService() 来启动 IntentService,可能因为你想通过 Binder 或 Messenger 使得 IntentService 和 Activity 可以通信,这样那么 onHandleIntent() 不会被回调,相当于在你使用 Service 而不是 IntentService。
    3、为什么多次启动 IntentService 会顺序执行事件,停止服务后,后续的事件得不到执行?
    IntentService 中使用的 Handler、Looper、MessageQueue 机制把消息发送到线程中去执行的,所以多次启动 IntentService 不会重新创建新的服务和新的线程,只是把消息加入消息队列中等待执行,而如果服务停止,会清除消息队列中的消息,后续的事件得不到执行。

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
    
    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }
    

    具体参考GitHub上项目
    文/孙福生微博(简书作者)原文链接:http://www.jianshu.com/p/332b6daf91f0著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

    文/goeasyway(简书作者)原文链接:http://www.jianshu.com/p/7a7db9f8692d著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

    相关文章

      网友评论

          本文标题:Android面试一天一题(1 Day):service

          本文链接:https://www.haomeiwen.com/subject/kpchpttx.html