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

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

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

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

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

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

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


Android面试一天一题(1 Day):service_第1张图片

当我遇到面试者知道怎么使用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 那样启动 IntentService
Intent startIntent = new Intent(getActivity(), MyIntentService.class);
getActivity().startService(startIntent);

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

Android面试一天一题(1 Day):service_第2张图片
图一

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


Android面试一天一题(1 Day):service_第3张图片

图二

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


Android面试一天一题(1 Day):service_第4张图片

图三

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


Android面试一天一题(1 Day):service_第5张图片

图四

查看源码
下面,从这几个功能点查看下源码:
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)