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可以动态的调试你的应用程序,如果你不做其他的额外处理 ...
随机推荐
- 一个用得比较广的微信API的XXE外部实体注入漏洞
文件地址: https://github.com/dodgepudding/wechat-php-sdk/raw/master/wechat.class.php 代码: <?php /** * ...
- George and Cards
Codeforces Round #227 (Div. 2) E:http://codeforces.com/contest/387/problem/E 题意:给你一个n个数的序列,然后给你一个标准序 ...
- Python4Delphi也是与VCL密切相关,所以才能相互调用,绝对有研究价值!
Python4Delphi也是与VCL密切相关,所以才能相互调用,绝对有研究价值! http://www.cnblogs.com/GarfieldTom/archive/2013/01/17/2864 ...
- struts2+jquery+json集成
以下采用struts2+jquery+json模拟一个案例.当点击提交按钮时会把输入的数据提交到后台,然后从后台获取数据在客户端显示. 效果如下: 接下来为struts2+jquery+json集成步 ...
- 设置Git提交时不用输入用户名和密码
在用git提交时代码至github上时每次都要输入用户名和密码,当提交操作较为频繁时非常不方便,可以按下文中的介绍,设置成提交时不用输入用户名和密码: 1.在当前库下,已经运行过 git remote ...
- kernel解读之 pick_next_rt_entity
1328 static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq, 1329 struct rt_rq *rt_rq) 133 ...
- poj2228
这显然是一道环形dp的题目 处理环形我们都是要转化为线性来做 一般有这么两种方法处理 复制一段到最后 (比如说noip的能量项链) 考查环形对dp的影响然后分类讨论(比如bzoj1040) 这道题我们 ...
- UVA- 1504 - Genghis Khan the Conqueror(最小生成树-好题)
题意: n个点,m个边,然后给出m条边的顶点和权值,其次是q次替换,每次替换一条边,给出每次替换的边的顶点和权值,然后求出这次替换的最小生成树的值; 最后要你输出:q次替换的平均值.其中n<30 ...
- 数据结构(trie,启发式合并):HDU 5841 Alice and Bob
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAABJEAAAE6CAIAAAApz1RvAAAgAElEQVR4nO3d3css1b3g8fyTdbHJbD
- 【动态规划】Vijos P1616 迎接仪式
题目链接: https://vijos.org/p/1616 题目大意: 长度为N的字符串,只含‘j’和‘z’,可以将任意两个字符调换K次,求能够拥有的最多的'jz'串. 题目思路: [动态规划] 首 ...