Android线程和线程池(2)——IntentService

一.IntentService的介绍

很多时候我们创建Service就是希望Service能在后台进行一些耗时操作,但是又由于Service默认是运行在主线程的,我们不太方便直接进行耗时操作,然后每次进行耗时操作我们都得手动创建新的线程,显得很麻烦。
所以就会有IntentService,它内部是帮我们创建了一个HandleThread,以及对应的Handler,并通过onStart方法把intent封装成message再send给了Handler,Handler就在他的handleMessage中会将Intent从Message中取出来,然后直接调用我们重写的onHandlerIntent去处理这个intent。
我们只需要在 onHandleIntent() 方法中去实现我们的业务代码即可。调用就是通过简单的startService去调用。

  • 它本质是一种特殊的Service,继承自Service并且本身就是一个抽象类
  • 它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止
  • 它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务
  • 它内部通过HandlerThread和Handler实现异步操作
  • 创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作

二.IntentService的使用姿势

  • 通过借助Intent调用startService方法启动 IntentServcie并传递传递数据到IntentService
  • startService(context, intentServcie.class)方法调用后,会把任务通过Intent传递到onHandleIntent() ,这个方法就是在子线程中运行的,进行一些网络请求等的耗时操作。
  • 最后通过接口+Handler转换到主线程更新UI(也可通过Broadcast等方法传递消息到Activity)

看下面的demo:
通过IntentService从网上获取图片并显示

public class MyIntentService extends IntentService {
    
    


    private static final String ACTION_FOO = "com.example.a11_text1.action.FOO";

    private static final String EXTRA_PARAM1 = "com.example.a11_text1.extra.PARAM1";
    private static final String EXTRA_PARAM2 = "com.example.a11_text1.extra.PARAM2";
	//接口,用于更新ui时传递数据到Activity
    public static UpdateUI updateUI;
    private static int index;

    public MyIntentService() {
    
    
        super("MyIntentService");
    }
	
    public static void setUpdateUI(UpdateUI updateUIInterface){
    
    
        updateUI=updateUIInterface;
    }
    public static void startActionFoo(Context context, String param1,int i) {
    
    
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_FOO);
        intent.putExtra(EXTRA_PARAM1, param1);
        intent.putExtra(EXTRA_PARAM2,i);
        index = i;
        context.startService(intent);
    }
	
    @Override
    protected void onHandleIntent(Intent intent) {
    
    

        if (intent != null) {
    
    
            final String action = intent.getAction();
            if (ACTION_FOO.equals(action)) {
    
    
                final String param1 = intent.getStringExtra(EXTRA_PARAM1);
                handleActionFoo(param1);
            }
        }
    }

    private void handleActionFoo(String param1) {
    
    
        Bitmap bitmap = downloadUrlBitmap(param1);
        Message msg = new Message();
        ImageModel imageModel = new ImageModel();
        imageModel.setBitmap(bitmap);
        msg.obj = imageModel;
        msg.what = index;
        if(updateUI!=null){
    
    
            updateUI.upData(msg);
        }
    }

    private Bitmap downloadUrlBitmap(String param1) {
    
    
        FutureTarget<Bitmap> futureTarget =
                Glide.with(getApplicationContext())
                        .asBitmap()
                        .load(param1).submit(100,100);

        Bitmap bitmap = null;
        try {
    
    
            bitmap = futureTarget.get();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return bitmap;
    }
}
 public class MainActivity extends AppCompatActivity  {
    
    

    Handler mainHandler,workHandler;
    HandlerThread mHandlerThread;


    private final String[] url ={
    
    
            "https://img-blog.csdn.net/20160903083245762",
            "https://img-blog.csdn.net/20160903083252184",
            "https://img-blog.csdn.net/20160903083257871",
            "https://img-blog.csdn.net/20160903083257871",
            "https://img-blog.csdn.net/20160903083311972",
            "https://img-blog.csdn.net/20160903083319668",
            "https://img-blog.csdn.net/20160903083326871"
    };
    private ImageView imageView;

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

        initView();
        initData();

    }
    private void initView() {
    
    
        imageView= (ImageView) findViewById(R.id.image);
        mainHandler = new Handler(getMainLooper()){
    
    
            @Override
            public void handleMessage(@NonNull Message msg) {
    
    
                ImageModel model = (ImageModel) msg.obj;
                if(model.getBitmap()!=null){
    
    
                    imageView.setImageBitmap(model.getBitmap());
                }
            }
        };
    }
    private void initData() {
    
    
    
        IS_DownloadThePictures();

    }

    private void IS_DownloadThePictures() {
    
    
        UpdateUI updateUI = message -> mainHandler.sendMessageDelayed(message, 1000);
        MyIntentService.setUpdateUI(updateUI);
        for(int i = 0; i < 7; i++){
    
    
            startActionFoo(MainActivity.this,url[i],i);
        }
    }
}

注意即使我们多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的,最终IntentService会去调用onHandleIntent执行异步任务。这里可能我们还会担心for循环去启动任务,而实例又只有一个,那么任务会不会被覆盖掉呢?其实是不会的,因为IntentService真正执行异步任务的是HandlerThread+Handler,每次启动都会把下载图片的任务添加到依附的消息队列中,最后由HandlerThread+Handler去执行。

三.源码分析

我们先来看看IntentService的onCreate方法:

	@Override
    public void onCreate() {
    
    
      
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

当第一次启动IntentService时,它的onCreate方法将会被调用,其内部会去创建一个HandlerThread并启动它,接着创建一个ServiceHandler(继承Handler),传入HandlerThread的Looper对象,这样ServiceHandler就变成可以处理异步线程的执行类了。那么IntentService是怎么启动异步任务的呢?其实IntentService启动后还会去调用onStartCommand方法,而onStartCommand方法又会去调用onStart方法,我们看看它们的源码:

 public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    
    
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
public void onStart(@Nullable Intent intent, int startId) {
    
    
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

在onStart方法中,IntentService通过mServiceHandler的sendMessage方法发送了一个消息,这个消息将会发送到HandlerThread中进行处理(因为HandlerThread持有Looper对象,所以其实是Looper从消息队列中取出消息进行处理,然后调用mServiceHandler的handleMessage方法),我们看看ServiceHandler的源码:

private final class ServiceHandler extends Handler {
    
    
        public ServiceHandler(Looper looper) {
    
    
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
    
    
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

在这里面调用了onHandleIntent方法,执行我们要执行的异步操作,执行结束后,IntentService会通过 stopSelf(int startId)方法来尝试停止服务。这里采用stopSelf(int startId)而不是stopSelf()来停止服务 ,是因为stopSelf()会立即停止服务,而stopSelf(int startId)会等待所有消息都处理完后才终止服务

这里要注意的是如果后台任务只有一个的话,onHandleIntent执行完,服务就会销毁,但如果后台任务有多个的话,onHandleIntent执行完最后一个任务时,服务才销毁。最后我们要知道每次执行一个后台任务就必须启动一次IntentService,而IntentService内部则是通过消息的方式发送给HandlerThread的,然后由Handler中的Looper来处理消息,而Looper是按顺序从消息队列中取任务的,也就是说IntentService的后台任务时顺序执行的,当有多个后台任务同时存在时,这些后台任务会按外部调用的顺序排队执行,

四.面试题归纳

  1. 为何不用bindService方式创建IntentService?
    IntentService的工作原理是,在IntentService的onCreate()里会创建一个HandlerThread,并利用其内部的Looper实例化一个ServiceHandler对象;而这个ServiceHandler用于处理消息的handleMessage()方法会去调用IntentService的onHandleIntent(),这也是为什么可在该方法中处理后台任务的逻辑;当有Intent任务请求时会把Intent封装到Message,然后ServiceHandler会把消息发送出,而发送消息是在onStartCommand()完成的,只能通过startService()才可走该生命周期方法,因此不能通过bindService创建IntentService。

猜你喜欢

转载自blog.csdn.net/haazzz/article/details/115109685