Android 后台Service下载

一、前言
        原理其实大家都懂,只不过没动手实际好好的写过,项目中也没有涉及到用这块内容,所以....所以被人问及细节时,就说不清个123了,为了一改我的慵懒,因此,我写这篇文章,至少下次再被问起时,不会尴尬。
        本篇文章会涉及到以下知识点:
        1. Service (两种启动方法,对应的不同生命周期不同);
        2. Binder;
        3. Activity如何与Service交互;
        4. Service如何更新带进度条的状态栏;
二、Service & Binder
        2.1 Service
        Service有两个方法来启动:startService 和 bindService,采用不同的方法,service的生命周期也不同(本篇只讲同进程,不讲跨进程):
        1. startService启动,其生命周期不会因启动它的组件Destroy而消亡,而是依赖于mainThread(即应用主线程),一但主线程退出,即代表整个应用退出,因为Service就会Destroy。
        2. bindService启动,其生命周期依赖启动它的组件,组件Destroy时,Service也随之一起Destroy。
        2.2 Binder
        Binder是Android系统中一个重要的“设备”,之所以加引号,实际上它是虚拟出来的,类似于Linux中的块设备,因此,它也是基于IO的。
        Binder在Android中,是被用做进程间通信使用的,而且,Binder是Parcelable的,通过Transaction,与它的代理端,即Binder Server端交互,本章只是简单的使用Binder来做同一进程中的线程间通信。
三、Activity与Service交互
        Question:如何将Service用做后台 下载,其生命周期不依赖启动它的组件,且能够与它的组件相互通信?
        分析问题:
        该问题,表述了三点信息:
        1. 后台下载;
        2. 生命周期不依赖其它组件;
        3. 数据交互;
        3.1 后台下载
        通常,我们使用Service,会有这么几点需求:
        1. 若是前台Service,一般是用来做类似于音乐播放器的;
        2. 若是后台Service,则通常是用来和服务器进行交互(数据下载),或是其它不需要用户参与的操作;
        同一进程中,启动Service,若直接与服务器交互,则很容易引起ANR,因为,Service是由mainThread创建出来,因此,此时Service是运行在UI主线程的,如果需要联网下载,则需要开启一个Thread,然后在子线程中来运行。在Service中创建/使用线程,与在Activity中一样,无区别。
        3.2 生命周期不依赖其它组件
        这点,我前面说过了,使用startService来启动该service就行;
        3.3 数据交互
        组件通常是Activity,可以通过bindService,当成功绑定时,可以获取Service中定义后的一个IBinder接口,我们可以通过这个接口,返回该Service对象,从而,可以直接访问该Service中的公有方法;
        当Service想要把数据传递给某个组件时,最简单最好的办法就是通过Broadcast,在Intent中带上数据,广播给组件即可(记住,BroadcastReceiver中,onReceive也不能运行太久,否则也会ANR,只有10秒哦)。
四、Service刷新带有进度条的状态栏
        通常,我们会发一些Notification到系统状态栏上,以提醒用户做一些事情,但是,如果大家仔细看了Notification的参数,就会发现里面有一个RemoteViews类型的成员,是不是有点像在哪见过?对的,如果你做个Widget应用,那么RemoteViews你应该很熟悉:
        RemoteViews可以让我们自定义一个View,里面放一些小的控件,系统有定义的,不是所有的控件都能放!那么,我们就可能自定义一个带有ProgressBar的layout,然后绑定到Notification对象上,并通过NotificationManager来通知更新即可。
        注:网上有提醒说,建议不要更新太频繁,否则会使系统很卡!
五、用例子说话
        本节,就将写一个Demo,带大家一起了解如何活用以上这些概念,能够让大家应用到将来自己的项目中。文件不多,三个类,一个Service,一个Activity,和一个任务类(因为我在Service中,创建了一个线程队列,使用单线程来模拟)。
        5.1 DownloadManagerActivity
        对应的layout:
[ html
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    tools:context=".DownloadManagerActivity" > 
 
    <TextView 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_centerHorizontal="true" 
        android:layout_centerVertical="true" 
        android:text="@string/hello_world" /> 
     
    <Button  
        android:id="@+id/add_task" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="@string/addTask"/> 
     
    <Button  
        android:id="@+id/cancel_task" 
        android:layout_toRightOf="@id/add_task" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="@string/cancelTask"/> 
 
</RelativeLayout> 
         里面主要有两个Button,一个告诉Service添加任务,一个告诉Service取消指定的任务。
[java] 
public final static String TAG = "DownloadService"; 
private DownloadService mService = null; 
private static int task_count = 0; 
private final static String ACTION_UPDATE = "com.chris.download.service.UPDATE"; 
private final static String ACTION_FINISHED = "com.chris.download.service.FINISHED"; 
         几个对象,mService就是当bindService成功时,通过IBinder返回Service对象,ACTION_XXX用来接收Service发送的广播,在Activity中动态注册广播。
[java] 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_download_manager); 
     
    IntentFilter filter = new IntentFilter(); 
    filter.addAction(ACTION_UPDATE); 
    filter.addAction(ACTION_FINISHED); 
    registerReceiver(myReceiver, filter); 
     
    Intent it = new Intent(this, DownloadService.class); 
    startService(it); 
     
    Button add_task = (Button) findViewById(R.id.add_task); 
    add_task.setOnClickListener(new OnClickListener(){ 
        @Override 
        public void onClick(View arg0) { 
            TaskInfo ti = new TaskInfo(); 
            ti.setTaskId(task_count++); 
            ti.setTaskName(TAG + ti.getTaskId()); 
            ti.setProgress(0); 
            ti.setStatus(TaskInfo.WAITING); 
            mService.addTaskInQueue(ti); 
        } 
    }); 
     
    Button cancel_task = (Button) findViewById(R.id.cancel_task); 
    cancel_task.setOnClickListener(new OnClickListener(){ 
        @Override 
        public void onClick(View arg0) { 
            int index = (int) (Math.random() * task_count); 
            mService.cancelTaskById(index); 
        } 
    }); 
        一开始,动态注册一下BroadcastReceiver,指定接收两个ACTION;然后,startService启动一个Service。自定义BroadcastReceiver:
[java]
private BroadcastReceiver myReceiver = new BroadcastReceiver(){ 
    @Override 
    public void onReceive(Context context, Intent intent) { 
        if(intent.getAction().equals(ACTION_UPDATE)){ 
            int progress = intent.getIntExtra("progress", 0); 
            Log.d(TAG, "myReceiver - progress = " + progress); 
        }else if(intent.getAction().equals(ACTION_FINISHED)){ 
            boolean isSuccess = intent.getBooleanExtra("success", false); 
            Log.d(TAG, "myReceiver - success = " + isSuccess); 
        } 
    } 
}; 
        在onResume时,去bindService:
[java] 
@Override 
protected void onResume() { 
    super.onResume(); 
    Log.d(TAG, "Activity onResume"); 
     
    Intent it = new Intent(this, DownloadService.class); 
    bindService(it, mServiceConn, BIND_AUTO_CREATE); 
        并在onDestroy时,unbindService,以及unregisterReceiver:
[java]
@Override 
protected void onDestroy() { 
    super.onDestroy(); 
    unbindService(mServiceConn); 
    //stopService(new Intent(this, DownloadService.class)); 
    unregisterReceiver(myReceiver); 
        ServiceConnection代码:
[java] 
public ServiceConnection mServiceConn = new ServiceConnection(){ 
    @Override 
    public void onServiceConnected(ComponentName name, IBinder service) { 
        mService = ((DownloadService.ServiceBinder)service).getService(); 
        Log.d(TAG, "onServiceConnected: mService = " + mService); 
         
        if(mService != null){ 
            mService.notifyToActivity(false, true); 
        } 
    } 
 
    @Override 
    public void onServiceDisconnected(ComponentName name) { 
        mService = null; 
    } 
}; 
        如果成功了,就通过IBinder接口,获得Service对象。
        5.2 DownloadService
        继承Service类,override一些方法:
[java] 
@Override 
public IBinder onBind(Intent intent) { 
    Log.d(TAG, "onBind"); 
    return mBinder; 
 
@Override  
public int onStartCommand(Intent intent, int flags, int startId) {      
    Log.d(TAG, "onStartCommand"); 
    return START_STICKY; 
 
@Override 
public void onCreate() { 
    super.onCreate(); 
    Log.d(TAG, "onCreate"); 
    mBinder = new ServiceBinder(); 
    mDownloadQueue = new ArrayList<TaskInfo>(); 
    mNotificationManager = (NotificationManager) getSystemService( 
            android.content.Context.NOTIFICATION_SERVICE); 
    mNotification = new Notification(); 
    mRemoteView = new RemoteViews(this.getPackageName(), R.layout.remote_view_layout); 
 
@Override 
public void onDestroy() { 
    super.onDestroy(); 
    mBinder = null; 
    mDownloadQueue = null; 
    mNotificationManager = null; 
    mNotification = null; 
    mRemoteView = null; 
    Log.d(TAG, "onDestroy"); 
        我们通过startService来启动,因此,启动流程为:onCreate -> onStartCommand(注:onStart在API5以后,就不在用了,取而代之的是onStartCommand)。
        然后,我们bindService,此时service已经启动,所以,只会调用onBind。
        通常,我们应该在onCreate中,去完成一些初始化,而在onDestroy中,去释放这些内存,因为一但Service运行起来,再去掉startService或bindService,系统就不会再去调用onCreate了,但是onStartCommand或onBind仍旧会被调用。
        内部类ServiceBinder,只有一个公有方法,用来返回当前的Service对象:
[java] 
public class ServiceBinder extends Binder{ 
    public DownloadService getService(){ 
        return DownloadService.this; 
    } 
        提供给外部 组件的公有方法:
[java] 
public void notifyToActivity(boolean update, boolean finished){ 
    bNotifyWhenUpdate = update; 
    bNotifyWhenFinished = finished; 
 
public void addTaskInQueue(TaskInfo ti){ 
    if(mDownloadQueue != null){ 
        mDownloadQueue.add(ti); 
        Log.d(TAG, "addTaskInQueue id = " + ti.getTaskId()); 
    } 
     
    if(isRunning == false && mDownloadQueue.size() > 0){ 
        startDownload(); 
    } 
 
public void cancelTaskById(int id){ 
    Log.d(TAG, "cancelTaskById id = " + id); 
    for(int i = 0; i < mDownloadQueue.size(); i ++){ 
        TaskInfo ti = mDownloadQueue.get(i); 
        if(ti.getTaskId() == id){ 
            if(ti.getStatus() == TaskInfo.RUNNING){ 
                ti.setStatus(TaskInfo.CANCELED); 
            }else{ 
                mDownloadQueue.remove(i); 
            } 
            break; 
        } 
    } 
        三个方法:添加任务,取消任务,是否需要通知给已经绑定的组件。
        接下来,就是我们的线程了,这里的线程是单线程,使用私有的线程队列
[java]
private void startDownload(){ 
    if(isRunning){ 
        return; 
    } 
     
    new Thread(new Runnable(){ 
        @Override 
        public void run() { 
            while(mDownloadQueue != null && mDownloadQueue.size() > 0){ 
                isRunning = true; 
                 
                TaskInfo ti = mDownloadQueue.get(0); 
                while(ti.getProgress() < 100 && ti.getStatus() != TaskInfo.CANCELED){ 
                    Message msg = mHandler.obtainMessage(DOWNLOAD_STATUS_UPDATE, ti); 
                    mHandler.sendMessage(msg); 
                    try { 
                        Thread.sleep(1000); 
                    } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                    } 
                    ti.setProgress(ti.getProgress()+10); 
                } 
                 
                if(ti.getProgress() == 100 && mDownloadQueue.size() == 1){ 
                    Log.d(TAG, ti.getTaskName() + " is finished!"); 
                    Message msg = mHandler.obtainMessage(DOWNLOAD_STATUS_SUCCESS, ti); 
                    mHandler.sendMessage(msg); 
                }else if(ti.getStatus() == TaskInfo.CANCELED){ 
                    Log.d(TAG, ti.getTaskName() + " is canceled!"); 
                } 
                if(mDownloadQueue != null){ 
                    mDownloadQueue.remove(ti); 
                } 
            } 
            isRunning = false; 
        } 
    }).start(); 
        通过Thread.sleep(1000)来模拟网络,并使用Thread / Handler的模式,来更新Notification的RemoteViews。
       Handler的实现:
[java] 
private Handler mHandler = new Handler(){ 
    @Override 
    public void handleMessage(Message msg) { 
        switch(msg.what){ 
        case DOWNLOAD_STATUS_UPDATE: 
        { 
            mNotification.icon = R.drawable.ic_launcher; 
            mNotification.when = System.currentTimeMillis(); 
            mNotification.tickerText = "开始下载..."; 
            // 放置在"正在运行"栏目中    
            mNotification.flags = Notification.FLAG_ONGOING_EVENT; 
             
            TaskInfo ti = (TaskInfo) msg.obj; 
            Log.d(TAG, "update : progress = " + ti.getProgress()); 
            mRemoteView.setImageViewResource(R.id.ivIcon, R.drawable.ic_launcher); 
            mRemoteView.setTextViewText(R.id.tvName, ti.getTaskName()); 
            mRemoteView.setProgressBar(R.id.pbProgress, 100, ti.getProgress(), false); 
            mRemoteView.setTextViewText(R.id.tvProgress, ti.getProgress() + "%"); 
            mNotification.contentView = mRemoteView; 
            mNotificationManager.notify(NOTIFY_ID, mNotification); 
             
            notifyUpdate(ti); 
            break; 
        } 
         
        case DOWNLOAD_STATUS_SUCCESS: 
        { 
            mNotification.flags = Notification.FLAG_AUTO_CANCEL; 
            mNotification.contentView = null; 
            Intent it = new Intent(DownloadService.this, DownloadManagerActivity.class); 
            PendingIntent pi = PendingIntent.getActivity(DownloadService.this, 0, it, PendingIntent.FLAG_UPDATE_CURRENT); 
            mNotification.setLatestEventInfo(DownloadService.this, "下载完成", "文件已下载完毕", pi); 
            mNotificationManager.notify(NOTIFY_ID, mNotification); 
             
            notifyFinished(true); 
            break; 
        } 
         
        case DOWNLOAD_STATUS_FAILED: 
        { 
            mNotification.flags = Notification.FLAG_AUTO_CANCEL; 
            mNotification.contentView = null; 
            Intent it = new Intent(DownloadService.this, DownloadManagerActivity.class); 
            PendingIntent pi = PendingIntent.getActivity(DownloadService.this, 0, it, PendingIntent.FLAG_UPDATE_CURRENT); 
            mNotification.setLatestEventInfo(DownloadService.this, "下载失败", "", pi); 
            mNotificationManager.notify(NOTIFY_ID, mNotification); 
             
            notifyFinished(false); 
            break; 
        } 
         
        default: 
            break; 
        } 
    } 
}; 
        通知组件新的情况:
[java] 
private void notifyUpdate(TaskInfo ti){ 
    if(bNotifyWhenUpdate){ 
        Intent it = new Intent(ACTION_UPDATE); 
        it.putExtra("progress", ti.getProgress()); 
        DownloadService.this.sendBroadcast(it); 
    } 
 
private void notifyFinished(boolean isSuccess){ 
    if(bNotifyWhenFinished){ 
        Intent it = new Intent(ACTION_FINISHED); 
        it.putExtra("success", isSuccess); 
        DownloadService.this.sendBroadcast(it); 
    } 
        5.3 TaskInfo类
[java] 
package com.chris.download.service.Bean; 
 
import java.io.Serializable; 
 
public class TaskInfo implements Serializable { 
 
    private static final long serialVersionUID = -2810508248527772902L; 
 
    public static final int WAITING = 0; 
    public static final int RUNNING = 1; 
    public static final int CANCELED = 2; 
     
    private int taskId; 
    private String taskName; 
    private int progress; 
    private int status; 
     
    public int getTaskId() { 
        return taskId; 
    } 
    public void setTaskId(int taskId) { 
        this.taskId = taskId; 
    } 
    public String getTaskName() { 
        return taskName; 
    } 
    public void setTaskName(String taskName) { 
        this.taskName = taskName; 
    } 
    public int getProgress() { 
        return progress; 
    } 
    public void setProgress(int progress) { 
        this.progress = progress; 
    } 
    public int getStatus() { 
        return status; 
    } 
    public void setStatus(int status) { 
        this.status = status; 
    } 
六、总结
        本篇只是带大家入门,仍有许多可以改进的地方,如:使用多线程以及如何同步线程队列,多线程对应在状态栏上的多个RemoteViews更新,Activity中显示下载任务队列及其各任务的状态等。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值