下载组件优势
易接入、易用性
扩展性好(网络库扩展、文件IO扩展、调节线程数、众多可选参数)
网络和文件IO优化,体现下载速度较快
读写分离
无任何第三方依赖
通知栏的简易对接
下载组件的执行逻辑
Demo地址 http://www.coolapk.com/apk/com.vanda_adm.vanda
开源地址 http://gitlab.alibaba-inc.com/uc-mobile-open/QuarkDownloader
易接入 如何接入 在QuarkDownloader设计的时候,就易用性、低的接入成本经过了考虑,我们先看看App如何接入QuarkDownloader。
1 2 3 4 5 6 7 8 9 10 public class VandaApplication extends Application { @Override public void onCreate() { super .onCreate(); String defaultDownloadPath = PathConfig .getDownloadRootPath();QuarkDownloader .init(VandaApplication .this , defaultDownloadPath, new OkhttpNetworkConnect .Creator ()); } }
这里是给配置下载进程所需要的上下文参数,这些初始化是不耗时操作。这样就已经配置好了下载所必要的环境。
如果不需要外接网络库,则如下:
1 QuarkDownloader.init (VandaApplication.this , defaultDownloadPath);
该方式使用系统内置网络库(URLConnection)
使用下载组件来下载文件 启动一个下载任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void startTask(String url , String title ) { QuarkDownloadRequest request = new QuarkDownloadRequest .Builder() .url(url) .title(title) .build() ; QuarkDownloader . getInstance() .createTask(request ) .addOnStateChangeListener(new OnStateChangeListener() { @Override public void onStateChange(QuarkDownloadTask task , int status , long sofar , long total ) { } }).start() ; }
这样就建立一个下载任务,你只要在回调中处理你自己的逻辑就OK了。这里的例子只是下载组件最基本的启动方式,下载组件还提供了众多的参数,以及支持众多的场景。
扩展性好 网络库扩展、IO扩展 下载组件能够支持外部网络库接入,这也就说明,下载组件可以支持更多的下载协议,下载组件只关心数据读取、数据的存储。
下载组件抽象了一组接口,外部可以提供自己的网络库实现,如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 public interface NetworkConnection { int NO_RESPONSE_CODE = 0 ; enum RequestWays { POST, GET, } void addHeader (String name, String value) ; Map<String, List<String>> getRequestHeaderFields () ; Map<String, List<String>> getResponseHeaderFields () ; String getResponseHeaderField (String name) ; void execute (RequestWays requestWays, Map<String, String> postBody) throws IOException; int getResponseCode () throws IOException; void ending () throws Throwable; void outputStream (OutputStream outputStream) ; long read (long byteCount) throws IOException; void emit () throws IOException; void release () throws IOException; @Deprecated boolean dispatchAddResumeOffset (String etag, long offset) ; @Deprecated InputStream getInputStream () throws IOException; }
Okhttp实现 下载组件进行了抽象接口,来实现最大化的定制需求,方便业务自己的需求。为了更为方便的说明问题,我们列举一个使用Okhttp来实现网络库和IO操作的实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 class OkhttpNetworkConnect implements NetworkConnection { private Request.Builder mRequestBuilder; private Call mCall; private Response mResponse; private String mUrl; private BufferedSink mSink = null ; private BufferedSource mSource = null ; private Buffer mBuffer = null ; public OkhttpNetworkConnect (String url) throws IOException { mRequestBuilder = new Request .Builder(); this .mUrl = url; } @Override public void addHeader (String name, String value) { mRequestBuilder.addHeader(name, value); } @Override public boolean dispatchAddResumeOffset (String etag, long offset) { return false ; } @Override public InputStream getInputStream () throws IOException { return mResponse.body().byteStream(); } @Override public Map<String, List<String>> getRequestHeaderFields () { return null ; } @Override public Map<String, List<String>> getResponseHeaderFields () { return null ; } @Override public String getResponseHeaderField (String name) { return mResponse.header(name); } @Override public void execute (FileDownloadConnection.RequestWays requestWays, Map<String, String> postBody) throws IOException { mRequestBuilder.cacheControl(CacheControl.FORCE_NETWORK); FormBody.Builder builder = new FormBody .Builder(); RequestBody requestBody = null ; if (requestWays == RequestWays.POST && postBody != null ) { for (Map.Entry<String, String> entry : postBody.entrySet()) { builder.addEncoded(entry.getKey(), entry.getValue()); } requestBody = builder.build(); } mRequestBuilder.cacheControl(CacheControl.FORCE_NETWORK); final Request request = requestWays == RequestWays.POST ? mRequestBuilder.url(mUrl).post(requestBody).build() : mRequestBuilder.url(mUrl).get().build(); mCall = OkHttpProxy.getInstance().newCall(request); mResponse = mCall.execute(); mSource = mResponse.body().source(); Log.d("vanda" , "head = " + mResponse.headers().toString() + " mSource = " + mSource); } @Override public int getResponseCode () throws IOException { return mResponse.code(); } @Override public void ending () { if (mResponse != null ) { mResponse.close(); } } @Override public void outputStream (OutputStream outputStream) { mSink = Okio.buffer(Okio.sink(outputStream)); mBuffer = mSink.buffer(); } @Override public long read (long byteCount) throws IOException { return mSource.read(mBuffer, byteCount); } @Override public void emit () throws IOException { mSink.emit(); } @Override public void release () throws IOException { if (mSource != null ) { mSource.close(); } if (mSink != null ) { mSink.close(); } } public static class Creator implements NetworkConnectionCreator { public Creator () { } @Override public NetworkConnection create (String originUrl) throws IOException { return new OkhttpNetworkConnect (originUrl); } } }
如上代码就实现了Okhttp网络库的接入,接口实现也比较简单。我们注意到网络库涉及到网络IO这块,一些网络库在网络库IO有自己的实现,典型的例子是Okhttp使用了自身的Okio,所以在网络IO上我们把操作权交给了业务方,这样能够获取更为高效的网络IO性能。在文件IO上我们提供了内部已经计算好文件range和seek的OutputStream,业务方可以对其进行包装或者直接使用都能够获取更为高效的文件IO,至于为什么高效后面我会说到。
网络IO读取策略 一般来说我们利用空间和换取时间的策略,来提高网络IO的使用率,也就是说,我们使用内存空间来提高网络IO效率。如下图:
如上图,我们使用读取segment,一个segment有一个固定大小,下载组件中抽离了Okio的核心逻辑,组合成QuarkOkio,每次进行emit数据时必须满足上述条件,使用segment来存储数据,这样有3个好处:
每次读取segment时可以提供任务进度回调,而这个时候并没有进行文件IO操作,我们把数据保存在内存中
segment是一个双向链式结构,并且提供SegmentPool来缓存segment,避免系统GC和申请byte时的zero-fill。,这些数据块使用链表进行管理,这可以仅通过移动“指针”就进行数据的管理,而不用真正去处理数据,而且对扩容来说也十分方便。
它对数据进行了分块处理,这样在大数据IO的时候可以以块为单位进行IO,这可以提高IO的吞吐率。
同时我们下载组件提供了多线程下载机制,结合QuarkOkio数据缓存机制,能够最大程度利用网络IO,从而使网络IO最大化。
文件IO策略 内置众多文件IO方式,可以扩展 QuarkDownloader组件比对过Java里面的文件IO性能,我们先列举性能最优的IO,MappedByteBuffer,下载组件中有一个IO类IOChannel,这个类中继承了OutputStream,利用MappedByteBuffer来完成文件的IO,现在我们来说说这个和普通的IO有和不同。
简单介绍下传统IO的方式 IO,就是数据不停地搬入搬出缓冲区而已(使用了缓冲区)。比如,用户程序发起读操作,导致“ syscall read ”系统调用,就会把数据搬入到buffer中。用户发起写操作,导致 “syscall write ”系统调用,将会把buffer中的数据搬出去(发送到网络中或者写入到磁盘文件)
普通的IO处理的流程图:
将数据从磁盘读数据到内核缓冲区,由DMA来完成
然后内核将内核缓冲区的数据拷贝到用户空间的用户进程
这样完成了数据的读
一般Java应用程序读取数据代码如下:
1 2 3 4 5 byte [] b = new byte [4096 ];long len;while ((len = inputStream.read (b))>=0 ) { }
当执行到read()方法时,底层执行了很多操作的:
内核给磁盘控制器发指令:我要读磁盘上的某块磁盘块上的数据。
在DMA的控制下,把磁盘上的数据读入到内核缓冲区。
内核把数据从内核缓冲区拷贝到用户缓冲区。
从上面几步我们可以分析出:
就操作系统而言,JVM只是一个用户进程,处于用户态空间中。而处于用户态空间的进程是不能直接操作底层硬件的。而IO操作就需要操作底层的硬件,比如磁盘。因此,IO操作必须得借助内核的帮助才能完成(中断,trap),即:会有用户态到内核态的切换。
对于磁盘块的读取而言,每次访问磁盘读数据时,并不是读任意大小的数据的,而是每次读一个磁盘块或者若干个磁盘块(这是因为访问磁盘操作代价是很大的,而且我们也相信局部性原理) 因此,就需要有一个“中间缓冲区”–即内核缓冲区。先把数据从磁盘读到内核缓冲区中,然后再把数据从内核缓冲区搬到用户缓冲区。
内存映射 内存映射IO,也即JAVA NIO中提到的内存映射文件或者说 直接内存,示例图如下:
从上图可以看出:内核空间的 buffer 与 用户空间的 buffer 都映射到同一块 物理内存区域。
它的主要特点如下:
对文件的操作不需要再发read 或者 write 系统调用了
当用户进程访问“内存映射文件”地址时,自动产生缺页错误,然后由底层的OS负责将磁盘上的数据送到内存。
这就是是JAVA NIO中提到的内存映射缓冲区(Memory-Mapped-Buffer)它类似于JAVA NIO中的直接缓冲区(Directed Buffer)。MemoryMappedBuffer可以通过java.nio.channels.FileChannel.java(通道)的 map方法创建。
使用内存映射缓冲区来操作文件,它比普通的IO操作读文件要快得多。甚至比使用文件通道(FileChannel)操作文件 还要快。因为,使用内存映射缓冲区操作文件时,没有显示的系统调用(read,write),而且OS还会自动缓存一些文件页(memory page)
既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。 mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址。 前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中。 如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上。
总结来说,内存映射文件是将硬盘上文件的位置与进程逻辑地址空间中一块大小相同的区域之间一一对应, 建立内存映射由mmap()系统调用将文件直接映射到用户空间,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,要操作其中的数据时即第一次访问ptr指向的内存区域,必须通过MMU将逻辑地址转换成物理地址,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,这个过程只进行了一次数据拷贝。因此,内存映射的效率要比read/write调用效率高。
通过以上的 网络IO 和 文件IO 的策略,加上我们多线程并发处理,在下载速度上能够有一个较快的下载速度,尤其是在处理高速网络下载能力。
读写分离 QuarkDownloader实现了读写分离,同时提供了可选方式。全局一个线程来完成文件的写入。为了结合外部网络库的自身网络IO情况,内部保留了网络库的网络IO和文件IO的结合方式,典型的就是Okhttp的Okio
无任何第三方依赖 QuarkDownloader下载组件剔除了所有的依赖,目前下载组件没有任何第三方依赖,当然我们需要JDK在1.4以上。
通知栏接入和自定义 QuarkDownloader提供了通知栏的高度自定义化接口,同时支持任务是否要显示在通知栏,在下载组件内部有静默任务和非静默任务,非静默任务通过注册的通知栏接口产生回调。我们来看看如何实现自己的通知栏。
QuarkDownloader抽象了一个通知栏通用接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public interface OnShowNotification { boolean pauseOrRemove () ; void show (final int status, final BaseDownloadTask baseDownloadTask) ; void cancel () ; }
非静默任务会根据接口的实现来回调通知栏。
如何接入通知栏 我们提供一组自定义的通知栏显示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 public class DownloadNotificationManager implements QuarkDownloadNotificationManager.OnShowNotification { public static final int DOWNLOAD_NOTIFICATION_ID = 0x1213 ; private static final String PATTERN = "mm':'ss" ; private static final long ONE_HOUR = 60 * 60 * 1000 ; private static final long TIME_INTERVAL = 5 * 1000 ; public static final String OPEN_TYPE = "open_type" ; public static final String OPEN_TYPE_ENTER_DOWNLOAD_INTERFACE = "enter_download_interface" ; private static final String FORMAT_M = "%#.1fM/s" ; private static final String FORMAT_K = "%dK/s" ; private static final String FORMAT_NOTIFICATION_CONTENT = "%s%s%s%s/%s%s%s%s%s" ; private static final String FORMAT_SPLITE = " | " ; private static final String FORMAT_PERCENT = "%" ; private static final String STRING_ONE_HOUR = ">1 hour" ; private static final String STRING_UNKNOWN = "..." ; private static final String STRING_POINT = ". " ; private SimpleDateFormat mStringDateFormat; private Date mDate; private long mCurDownloadId = -1 ; private long mCurDownloadTime = -1 ; private int mDownloadingCount = 0 ; NotificationCompat.Builder mBuilder; private ArrayList<Long> mListId = new ArrayList<>() ; public DownloadNotificationManager() { mStringDateFormat = new SimpleDateFormat(PATTERN) ; mDate = new Date() ; Intent[] intents = new Intent[4 ] ; intents[0 ] = new Intent(ContextManager.getApplicationContext () , MainActivity .class ); intents[0 ] .putExtra(OPEN_TYPE, OPEN_TYPE_ENTER_DOWNLOAD_INTERFACE) ; PendingIntent pendingIntent[] = new PendingIntent[4 ] ; pendingIntent[0 ] = PendingIntent . getActivity(ContextManager.getApplicationContext () , 0 , intents[0 ] , PendingIntent.FLAG_UPDATE_CURRENT); mBuilder = new NotificationCompat. Builder(FileDownloadHelper.getAppContext () ); mBuilder.setOngoing(true ) .setLights(0, 0, 0) .setPriority(NotificationCompat.PRIORITY_MAX) .setContentIntent(pendingIntent [0]) .setWhen(0) .setAutoCancel(true ) .setSmallIcon(R.drawable .small_icon ) .setLargeIcon(BitmapFactory.decodeResource (ContextManager.getApplicationContext () .getResources() , R . drawable.ic_logo)); } private NotificationManager manager; public NotificationManager getManager() { if (manager == null) { manager = (NotificationManager) FileDownloadHelper . getAppContext() . getSystemService(Context.NOTIFICATION_SERVICE) ; } return manager; } private static long mTime = 0 ; private static long INTERVAL = 1000 ; @Override public void show(final int status, final BaseDownloadTask baseDownloadTask) { if (!SystemUtils . isOpenNotification || (SystemUtils . isFg && !SystemUtils . isShowNotificationMainUi)) { return; } long end = System . currentTimeMillis() ; long interval = end - mTime; if (interval < INTERVAL && status == QuarkDownloadTask.STATUS_PROGRESS) { return; } mTime = end ; ThreadManager . post(ThreadManager.THREAD_BACKGROUND, new Runnable() { @Override public void run() { showInBackgroundThread(status , baseDownloadTask ) ; } }); } private void showInBackgroundThread(int status , BaseDownloadTask baseDownloadTask ) { if (baseDownloadTask == null) { return; } if (status != FileDownloadStatus . completed && status != FileDownloadStatus . error && status != FileDownloadStatus . paused) { if (mCurDownloadId == -1 ) { mCurDownloadTime = System . currentTimeMillis() ; mCurDownloadId = baseDownloadTask.getId() ; mListId.add(mCurDownloadId); mDownloadingCount = QuarkDownloader . getInstance() .getDownloadingTaskCount() .length; } else if (mListId.contains((long) baseDownloadTask.getId() ) && mCurDownloadId != baseDownloadTask.getId() && ((System . currentTimeMillis() - mCurDownloadTime) < TIME_INTERVAL)) { return; } else if (mCurDownloadId != baseDownloadTask.getId() && (System . currentTimeMillis() - mCurDownloadTime) >= TIME_INTERVAL) { mCurDownloadTime = System . currentTimeMillis() ; } } mCurDownloadId = baseDownloadTask.getId() ; if (!mListId.contains(mCurDownloadId)) { mListId.add(mCurDownloadId); } switch (status) { case FileDownloadStatus . pending: mDownloadingCount = QuarkDownloader . getInstance() .getDownloadingTaskCount() .length; break; case FileDownloadStatus . started: mDownloadingCount = QuarkDownloader . getInstance() .getDownloadingTaskCount() .length; break; case FileDownloadStatus . progress: break; case FileDownloadStatus . retry: break; case FileDownloadStatus . error: if (mListId.contains(mCurDownloadId)) { mListId.remove(mCurDownloadId); } break; case FileDownloadStatus . paused: if (mListId.contains(mCurDownloadId)) { mListId.remove(mCurDownloadId); } break; case FileDownloadStatus . completed: if (mListId.contains(mCurDownloadId)) { mListId.remove(mCurDownloadId); } break; case FileDownloadStatus . warn: break; } if (mListId.size() != mDownloadingCount) { mDownloadingCount = QuarkDownloader . getInstance() .getDownloadingTaskCount() .length; } String speed; long sp = baseDownloadTask.getSpeed() ; if (sp > 1024 ) { speed = String . format(Locale.CHINESE, FORMAT_M, (float ) sp / (float ) 1024 ); } else { speed = String . format(Locale.CHINESE, FORMAT_K, sp); } long sofar = baseDownloadTask.getLargeFileSoFarBytes() ; long total = baseDownloadTask.getLargeFileTotalBytes() ; int progress = total <= 0 ? 0 : (int ) ((sofar / (float ) total) * 100 ); String title; String content; if (((status == FileDownloadStatus . completed && QuarkDownloader . getInstance() .isIdle() ) || status == FileDownloadStatus . error || status == FileDownloadStatus . paused) && mListId.size() == 0 ) { int [] completeAndPauseCount = QuarkDownloader . getInstance() .getCompleteAndPauseCount() ; title = FileDownloadHelper . getAppContext() .getString(R.string .app_name ) + " " + FileDownloadHelper . getAppContext() .getString(R.string .download ) ; content = (completeAndPauseCount[1 ] > 0 ? FileDownloadHelper . getAppContext() .getString(R.string .download_notification_task ) + " " + completeAndPauseCount[1 ] + FORMAT_SPLITE : "" ) + FileDownloadHelper . getAppContext() .getString(R.string .download_notification_complete ) + " " + completeAndPauseCount[0 ] ; mDownloadingCount = 0 ; mBuilder.setOngoing(false ) ; mBuilder.setProgress(0, 0, false ) ; notify(title, content, false ); } else { int index = mDownloadingCount > 1 ? getIndex(mListId , baseDownloadTask .getId () ) : -1 ; title = (index > 0 ? index + STRING_POINT : "" ) + baseDownloadTask.getFilename() ; long timeLeft = sp <= 0 ? (total - sofar) : (total - sofar) / sp; mDate.setTime(timeLeft ) ; String timeTips = timeLeft >= ONE_HOUR ? STRING_ONE_HOUR : mStringDateFormat.format(mDate); content = String . format(FORMAT_NOTIFICATION_CONTENT, total <= 0 ? STRING_UNKNOWN : progress + "" , FORMAT_PERCENT, FORMAT_SPLITE, QuarkFileUtlis . formatSize(sofar ) , QuarkFileUtlis . formatSize(total ) , FORMAT_SPLITE, speed, FORMAT_SPLITE, total <= 0 ? STRING_UNKNOWN : timeTips); mBuilder.setOngoing(true ) ; mBuilder.setProgress(1000, (int ) ((sofar / (float ) total) * 1000 ), total <= 0 ); notify(title, content, true ); } } public static int getIndex(List<Long> list , int id ) { if (list == null || list .size() == 0 ) { return -1 ; } for (int i = 0 ; i < list .size() ; i++) { if (list .get(i) == id) { return i + 1 ; } } return -1 ; } @Override public boolean pauseOrRemove() { ThreadManager . post(ThreadManager.THREAD_BACKGROUND, new Runnable() { @Override public void run() { pauseInBackgroundThread() ; } }); return true ; } private void pauseInBackgroundThread() { if (mListId == null || mListId.size() == 0 ) { int [] completeAndPauseCount = QuarkDownloader . getInstance() .getCompleteAndPauseCount() ; String title = FileDownloadHelper . getAppContext() .getString(R.string .app_name ) + " " + FileDownloadHelper . getAppContext() .getString(R.string .download ) ; String content = (completeAndPauseCount[1 ] > 0 ? FileDownloadHelper . getAppContext() .getString(R.string .download_notification_task ) + " " + completeAndPauseCount[1 ] + " | " : "" ) + FileDownloadHelper . getAppContext() .getString(R.string .download_notification_complete ) + " " + completeAndPauseCount[0 ] ; mDownloadingCount = 0 ; mBuilder.setOngoing(false ) ; mBuilder.setProgress(0, 0, false ) ; if (completeAndPauseCount[0 ] == 0 && completeAndPauseCount[1 ] == 0 ) { cancel() ; } else { notify(title, content, false ); } } } private void notify(String title, String content, boolean isShowFileDownloadProcess) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mBuilder.setContentTitle(content ) .setSubText(title ) ; } else { mBuilder.setContentTitle(title ) .setContentText(content ) ; } mBuilder.setNumber(mDownloadingCount ) ; Notification notification = mBuilder.getNotification() ; int smallIconId = ContextManager . getApplicationContext() .getResources() .getIdentifier("right_icon" , "id" , android .R.class .getPackage () .getName() ); if (smallIconId != 0 ) { if (notification.contentView != null) { notification.contentView.setViewVisibility(smallIconId , View.INVISIBLE) ; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (notification.bigContentView != null) { notification.bigContentView.setViewVisibility(smallIconId , View.INVISIBLE) ; } } } if (isShowFileDownloadProcess) { QuarkDownloader . getInstance() .notifyNotification(DOWNLOAD_NOTIFICATION_ID, notification ) ; } else { getManager() .notify(DOWNLOAD_NOTIFICATION_ID, notification); } } catch (Exception e) { e.printStackTrace() ; } } @Override public void cancel() { ThreadManager . removeRunnable(mCancleRunnable ) ; ThreadManager . post(ThreadManager.THREAD_BACKGROUND, mCancleRunnable); } private Runnable mCancleRunnable = new Runnable() { @Override public void run() { getManager() .cancel(DOWNLOAD_NOTIFICATION_ID); } }; }
这里的实现,业务方可以根据自己的业务需求自己来做出实现,这样非常灵活,QuarkDownloader只是把通知栏的抽象起来,提供给外部统一的处理入口。
接下来,我们只要初始化这个实现,就能够轻松的实现通知栏:
1 2 3 DownloadNotificationManager mDownloadNotificationManager = new DownloadNotificationManager() ; QuarkDownloadNotificationManager . getInstance() .setOnShowNotification(mDownloadNotificationManager ) ;
那么这样你的应用就拥有自己的通知栏。
下载组件的执行逻辑 这里我画了下下载组件的大致流程图: