DownloadProvider调试
由于身边的同事离职,最近又接手了一个模块,DownloadProvider, 也就是安卓中自带的下载管理。此模块的代码量比较少,但是最近阅读代码却发现还是由不少知识点的。之前同事在此模块做了一个关于DRM的需求,查看了一下其代码修改量也是比较大的,最近需要进行ROM移植,发现直接看此部分代码不知所云。
为了能够实现后期顺利移植改功能,首先需要对此模块的整体流程有个比较清晰的认识。于是在项目其他问题还比较少的最近两天,开始断断续续地调试代码,增加log,查看其运行流程。
首先看清单文件AndroidManifest.xml, 一堆权限声明在此,大概了解到了下载时有很多限制权限,比如说需要应用的签名和系统相同或者是系统应用
android:protectionLevel="signatureOrSystem" 也许也正是因为此,外部调用下载需要使用DownloadManager来进行,因此在刚开始时也从网络上查找了使用接口来进行下载的方法,基本就三点:
1、获取下载管理服务
DownloadManager manger=getSystemService(Context.DOWNLOAD_SERVICE) ;
2、构建下载请求对象
DownloadManager.Request down=new DownloadManager.Request (Url.parse(http://122.com/222.apk));
ps 这个http地址可以在网页上任意找一个下载文件,然后用审查元素,查看网页源码,获得下载URL地址。 3、加入下载队列
manger.enqueue(down);
由此三步即可最终调用downloadProvider模块来实现下载。
DownloadManager manger = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request down=new DownloadManager.Request (Uri.parse("http://www.romjd.com/Rom/Download/74058/0?from=onekey"));
manger.enqueue(down);
Toast.makeText(this," begin to download", Toast.LENGTH_LONG).show(); 此部分代码是放在拨号盘中进行调试,然后编译的,单独编译的apk需要和系统相同的签名才可以正常下载,这个没有尝试, 但是将downloadProvider中的权限都改为normal,以及注释掉插入数据库时的权限检查后都没有能成功下载,还是会报权限问题。 ------------------------------随意的分割线----------------------- 下面来分析一下下载管理流程,其大体实现是外部调用下载会先往数据库中插入许多跟下载相关的字段信息,然后启动service来使用消息机制,查询数据库中的内容,最后逐个启动线程进行下载, 下载的过程中会往状态栏发送通知,并更新下载进度,当所有的下载完成之后,service会自动关闭。 1、插入数据库
public Uri insert(final Uri uri, final ContentValues values) {
checkInsertPermissions(values); ---权限检查
long rowID = db.insert(DB_TABLE, null, filteredValues);--插入数据库 downloads 表
insertRequestHeaders(db, rowID, values);---插入头信息到数据库 request_headers表
notifyContentChanged(uri, match);---通知数据库变化,刚开始感觉此处没什么卵用,因为监听还没注册,这个是在服务中进行的,后来才知道开机启动后,服务就已经创建了。
context.startService(new Intent(context, DownloadService.class)); ---启动服务
return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
}
2、下载服务DownloadService运行
public void onCreate() {
super.onCreate(); if (mSystemFacade == null) {
mSystemFacade = new RealSystemFacade(this);---这个目前不知道具体用途,在通知栏的点击响应中有调用此类的方法发送广播
} mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); mUpdateThread = new HandlerThread(TAG + "-UpdateThread");
mUpdateThread.start();
mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);---服务中用来进行消息处理的handler,这个对调很关键。
Log.v(Constants.TAG, "Service onCreate mUpdateHandler "); mScanner = new DownloadScanner(this);---跟文件扫描相关的,没怎么关注 mNotifier = new DownloadNotifier(this);---通知栏相关
mNotifier.cancelAll(); mObserver = new DownloadManagerContentObserver();
getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
true, mObserver);---注册下载管理中数据库变化监听的观察着 }
public int onStartCommand(Intent intent, int flags, int startId) {
int returnValue = super.onStartCommand(intent, flags, startId); mLastStartId = startId;
enqueueUpdate();---更新队列,其实就是发了个消息,然后也没有具体正对这个的处理,但是会运行handleMessage中的代码
return returnValue;
}
private Handler.Callback mUpdateCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final int startId = msg.arg1; final boolean isActive;
synchronized (mDownloads) {
isActive = updateLocked();---加锁进行更新数据库,查询之前插入到数据库的内容,根据查询结果决定是否要启动下载
} if (msg.what == MSG_FINAL_UPDATE) {
// Dump thread stacks belonging to pool
for (Map.Entry<Thread, StackTraceElement[]> entry :
Thread.getAllStackTraces().entrySet()) {
if (entry.getKey().getName().startsWith("pool")) {
Log.d(TAG, entry.getKey() + ": " + Arrays.toString(entry.getValue()));
}
} // Dump speed and update details
mNotifier.dumpSpeeds(); Log.wtf(TAG, "Final update pass triggered, isActive=" + isActive
+ "; someone didn't update correctly.");
} if (isActive) {
// Still doing useful work, keep service alive. These active
// tasks will trigger another update pass when they're finished. // Enqueue delayed update pass to catch finished operations that
// didn't trigger an update pass; these are bugs.
enqueueFinalUpdate(); } else {
// No active tasks, and any pending update messages can be
// ignored, since any updates important enough to initiate tasks
// will always be delivered with a new startId. if (stopSelfResult(startId)) {
if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");
getContentResolver().unregisterContentObserver(mObserver);
mScanner.shutdown();
mUpdateThread.quit();
}
} return true;
}
};
private boolean updateLocked() {
final long now = mSystemFacade.currentTimeMillis(); boolean isActive = false;
long nextActionMillis = Long.MAX_VALUE; final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet()); final ContentResolver resolver = getContentResolver();
final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
null, null, null, null); try {
final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID);
while (cursor.moveToNext()) {
final long id = cursor.getLong(idColumn);
staleIds.remove(id); if (info.mDeleted) {---如果标记为删除的,则删除文件和数据库记录
// Delete download if requested, but only after cleaning up
if (!TextUtils.isEmpty(info.mMediaProviderUri)) {
resolver.delete(Uri.parse(info.mMediaProviderUri), null, null);
} deleteFileIfExists(info.mFileName);
resolver.delete(info.getAllDownloadsUri(), null, null); } else {---否则启动下载
// Kick off download task if ready
final boolean activeDownload = info.startDownloadIfReady(mExecutor); // Kick off media scan if completed
final boolean activeScan = info.startScanIfReady(mScanner); isActive |= activeDownload;
isActive |= activeScan;
} // Keep track of nearest next action
nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis);
}
} finally {
cursor.close();
} // Clean up stale downloads that disappeared
for (Long id : staleIds) {
deleteDownloadLocked(id);
} // Update notifications visible to user
mNotifier.updateWith(mDownloads.values());---更新到通知栏 return isActive;
}
public boolean startDownloadIfReady(ExecutorService executor) {
synchronized (this) {
final boolean isReady = isReadyToDownload();---根据状态确定是否可以进行下载
final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone(); if (isReady && !isActive) {
if (mStatus != Impl.STATUS_RUNNING) {
mStatus = Impl.STATUS_RUNNING;
ContentValues values = new ContentValues();
values.put(Impl.COLUMN_STATUS, mStatus);
mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
} mTask = new DownloadThread(mContext, mSystemFacade, mNotifier, this);
Log.v(TAG, "startDownloadIfReady new DownloadThread");
mSubmittedTask = executor.submit(mTask);
}
return isReady;
}
}
3、下载线程DownloadThread
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
wakeLock.setWorkSource(new WorkSource(mInfo.mUid));
wakeLock.acquire(); executeDownload();---执行下载 }
}
private void executeDownload() throws StopRequestException {
switch (responseCode) {
case HTTP_OK:
if (resuming) {
throw new StopRequestException(
STATUS_CANNOT_RESUME, "Expected partial, but received OK");
}
parseOkHeaders(conn);---解析头信息,主要是通过各种形式获取文件名字,其中最后如果实在无法找到合适的名字,会用随机算法生成,在命好名字之后,会以此新建一个空文件。
transferData(conn);---这个才是正在填充文件数据的地方,里面会计算当前空间是否足以容纳此文件,当然里面也包含了计算当前下载进度的内容。
return;
}
4、通知栏处理DownloadNotifier
这个类中是用来处理下载过程中在通知栏显示进度的,其中看了一下好久没有用的Notification类,以及实现点击跳转的PendingIntent, 当看到此处时,突然想起了当年刚开始搞安卓,在The Creative Life 公司做短信模块,经常要用到这个,时隔一年多,现在又遇到了,像是看到了老朋友,由于久未交谈,有点陌生,于是又查询了谷歌的SDK文档看了下,渐渐又熟悉起来。
PendingIntent.getBroadcast最终在DownloadReceiver类中得到响应处理点击通知栏中的下载进入事宜。
此处当时调试时还出现了一个让我纠结许久的小玩意, 都知道点击通知栏的下载进度之后,如果有多个下载任务,会跳转到下载管理界面,但是我在阅读代码的时候,看到代码的处理最后只是发送了一个广播:
mSystemFacade.sendBroadcast(appIntent);
但是始终没有找到广播响应的地方,于是反过来搜索看哪里有标明调用界面类的地方,也没有得到结果,于是还怀疑最终跳转到界面的代码是不是这个广播起的作用,将其屏蔽之后,果然跳转不了了。
思来想去只有在全部的安卓源码中进行搜索,最后在浏览器中发现了处理该广播发送Intent的代码,这才明白是在浏览器中进行的处理。再回头查看androidManifest中的界面文件接收的Intent果然如此:
<activity android:name="com.android.providers.downloads.ui.DownloadList"
<intent-filter>
<action android:name="android.intent.action.VIEW_DOWNLOADS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity> 有时候山重水复疑无路,但是换个思维想想就会柳暗花明又一村。
DownloadProvider调试的更多相关文章
- 网络断开后重连downloadProvider继续下载问题调试分析
最近在安卓4.4上遇到一个断开wifi后重新连接wifi, downloadProvider继续下载文件失败的问题.于是开始了解下载管理模块的断点续载功能: 1.首先,分析android lo ...
- C# Web应用调试开启外部访问
在用C#开发Web应用时有个痛点,就是本机用VS开启Web应用调试时外部机器无法访问此Web应用.这里将会介绍如何通过设置允许局域网和外网机器访问本机的Web应用. 目录 1. 设置内网访问 2. 设 ...
- NodeJs之调试
关于调试 当我们只专注于前端的时候,我们习惯性F12,这会给我们带来安全与舒心的感觉. 但是当我们使用NodeJs来开发后台的时候,我想噩梦来了. 但是也别泰国担心,NodeJs的调试是很不方便!这是 ...
- 微信公众号开发之VS远程调试
目录 (一)微信公众号开发之VS远程调试 (二)微信公众号开发之基础梳理 (三)微信公众号开发之自动消息回复和自定义菜单 前言 微信公众平台消息接口的工作原理大概可以这样理解:从用户端到公众号端一个流 ...
- 写出易调试的SQL(修订版)
h4 { background: #698B22 !important; color: #FFFFFF; font-family: "微软雅黑", "宋体", ...
- tomcat开发远程调试端口以及利用eclipse进行远程调试
一.tomcat开发远程调试端口 方法1 WIN系统 在catalina.bat里: SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compi ...
- Hawk 4.7 单步调试
单步调试的意义 已经编写的工作流,可能会因为某些外界环境的变化而出错,此时需要排除错误,我们可以使用单步调试. 单步调试的本质,相当于只使用前n个模块,这样就能看到每个步骤下,流的改变. 例子 还是上 ...
- Visual Studio 2012远程调试中遇到的问题
有的时候开发环境没问题的代码在生产环境中会某些开发环境无法重现的问题,或者需要对生产环境代码进行远程调试该怎么办? Vs已经提供给开发者远程调试的工具 下面简单讲讲该怎么用,前期准备:1.本地登录账户 ...
- iOS逆向工程之Hopper+LLDB调试第三方App
LLDB是Low Level Debugger的简称,在iOS开发的调试中LLDB是经常使用的,LLDB是Xcode内置的动态调试工具.使用LLDB可以动态的调试你的应用程序,如果你不做其他的额外处理 ...
随机推荐
- Can deep learning help you find the perfect girl?
Can deep learning help you find the perfect girl? One of the first things I did when I moved to Mont ...
- js 实现 aop
Aop又叫面向切面编程,用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点,这篇就通过下面这几个小例子,来说说AOP在js中的妙用. 1, 防止window.onloa ...
- 检查网口流量与前10名流量大IP
此脚本包含的功能有: 1.实时监控任意网卡的流量 2.统计10秒内平均流量 3.统计每个端口在10秒内的平均流量,基于客户端和服务端端口统计.可以看出哪些端口占流量比较大,对于web服务器,一般是80 ...
- PYTHON之批量文件指定字符串替换
在工作应用中,运维自动化的基础是标准化. 而标准化的工作,是难点,在公司相关部门的配合. 那么,在有标准化之后,相应的部署脚本,就比较好写了. 贡献一个在类似环境下可以运用的东东.. 当然,可以写得更 ...
- c# 集合ArrayList;特殊集合Stack、Queue
一) ArrayList 1.foreach遍历数组中各个元素,执行内部语句 2. 3. 4. myarry.Clear();//将集合清空 bool b = myarry.Contains(3 ...
- dns智能解析对网站排名的影响
网站排名是所有建站者都关系的问题,如何提升网站排名有很多因素,网站是否健康也与网站排名有关,下面智儒科技网站建设为你研究下如何判断自己的网站是否健康. 一般情况下,网站的排名在优化的基础上,怎么也上不 ...
- Microsoft Office 2013 激活方法
Microsoft Office 2013 激活方法 Microsoft Office 2013是微软的新一代Office办公软件,全面采用Metro界面,包括Word.PowerPoint.Ex ...
- SDUT 最短路径(二维SPFA)
http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=2622 #include<stdio.h& ...
- [转]stringstream的用法
使用stringstream对象简化类型转换C++标准库中的<sstream>提供了比ANSI C的<stdio.h>更高级的一些功能,即单纯性.类型安全和可扩展性.在本文中, ...
- SRM 398(1-250pt)
题意:有两个变量x和y,三种运算符+,*,-,组成等式"变量 运算符 变量 运算符 变量 运算符 变量",要求每个变量恰好出现两次,且等式的值为val的等式有多少个.注意不计算运算 ...