文档章节

Android 后台Service下载

henry-zhang
 henry-zhang
发布于 2015/05/27 10:49
字数 2348
阅读 61
收藏 0

一、前言

 

        原理其实大家都懂,只不过没动手实际好好的写过,项目中也没有涉及到用这块内容,所以....所以被人问及细节时,就说不清个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中显示下载任务队列及其各任务的状态等。


本文转载自:

henry-zhang
粉丝 2
博文 62
码字总数 1431
作品 0
海淀
私信 提问
Android中后台定时任务实现,即时数据同步问题思考!

如果你正在找Android后台定时任务实现,那么你找对了,但是其实如果你正在找Java后台任务实现,你就不会找到我的这个博客了.但是我的实现方式没有使用多少Android相关的东西.确实.但是如果你进来...

李海珍
2012/05/18
16.5K
2
Android Service 服务(一)—— Service

一、 Service简介 Service是android 系统中的四大组件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的级别差不多,但不能自己运行只能后台运行,并且可以和...

长平狐
2013/01/06
252
0
android 关于广播不能接收的问题

最近在做项目发现一个问题? 我在服务里面注册一个广播,用来接受下载成功和下载失败,服务是在application中启动的,在activity里面执行下载功能,下载成功和下载失败都会发出广播,让servi...

zhou_qian
2015/08/22
435
0
Notification使用详解之三:通过服务更新进度通知&在Activity中监听服务进度

上次我们讲到如何实现一个可更新的进度通知,实现的方式是启动一个线程模拟一个下载任务,然后根据任务进度向UI线程消息队列发送进度消息,UI线 程根据进度消息更新通知的UI界面。可是在实际...

MZHS
2013/11/28
61
0
Android-Service 服务详解

一、Service的简介 首先,相信很多Android开发者都知道Service是android 系统中的四大组件之一,它跟Activity的级别差不多,但是它只能后台运行,并且可以和其他组件进行交互。service可以在...

晚茜
2018/12/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何设计抗住100亿次请求的抢红包系统?(附GitHub代码)

1. 前言 前几天,偶然看到了 《扛住100亿次请求——如何做一个“有把握”的春晚红包系统”》一文,看完以后,感慨良多,收益很多。 正所谓他山之石,可以攻玉,虽然此文发表于2015年,我看到...

Java程序员之家
43分钟前
4
0
动图+源码,演示Java中常用数据结构执行过程及原理

最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于jdk8, 可能会有些特性与jdk7之前不相同, 例如LinkedList LinkedHashMap中的双向...

Java技术剑
今天
5
0
怎样在ps中制作对话气泡?一招教你轻松解决

PS是在工作中经常使用的平面设计软件,利用ps可以实现很多操作。换天,换发色,添加亮灯等操作都是比较常见的,今天将为大家分享怎样在ps中制作对话气泡的方法,希望能给大家带来帮助。 绘制...

干货趣分享
今天
2
0
EDI 电子数据交换全解指南

EDI(Electronic Data Interchange,电子数据交换)技术使得企业与企业(B2B)实现通信自动化,帮助交易伙伴和组织更快更好地完成更多工作,并消除了人工操作带来的错误。从零售商到制造商、物...

EDI知行软件
今天
3
0
CentOS7的LVM动态扩容

# 问题 CentOS7上面的磁盘空间有点紧张,需要扩容。 解决 查询当前磁盘状态 [root@xxx ~]# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTfd0 2:0 1 4K ...

亚林瓜子
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部