2.1 带有反劫持功能的下载SDK
在Android开发中,我们经常使用文件下载功能,比如,应用的版本更新等。对于下载功能,一般操作是通过文件下载链接建立网络请求,然后通过网络连接不断读取文件流信息并写到本地文件中,其中还会涉及断点续传等功能,在互联网上基本都能找到相关知识的介绍,因此本节不会详细展开这些内容。本节主要结合工作中的相关经验,介绍分段式下载和下载中的反劫持等的相关经验。
2.1.1 分段式多线程网络通信
多线程下载技术在下载中是一种很常见的技术方案,这种方案充分利用了多线程的优势,在同一时间段内通过多个线程发起下载请求,将需要下载的数据文件切割成多个部分,每一个线程只负责下载其中一个部分,然后将下载后的多个部分组装成完整的数据文件,这样便可以大大提高整体的下载效率。多线程下载提升下载效率的原理其实可以这么理解,相当于高速公路多车道比单车道车流量更大,因为同一时间内通过的车辆更多。
在多线程下载技术中,有一点非常重要,那就是如何分割文件,其实这就是本节要重点介绍的分段式多线程网络通信技术。
首先,需要介绍一下HTTP请求头中重要的头部定义,它可以告诉目标服务器本次网络请求的文件目标范围。因此,我们可以通过多个线程进行网络请求,然后为不同线程的下载请求设置不同的范围,从而达到分段式多线程网络通信的目的。
在分段式分割文件下载范围时,一般的处理方案是对文件大小进行等分处理,例如,目标文件大小是10MB,将其分割成4段同时下载,则分割后每段的数据范围如图2-1所示。
图2-1
对于等分分段处理,当然存在文件大小刚好不是分段数整数倍的情况,对于这种情况我们依然可以通过文件大小除以分段数并取整来获取每段的大小,只是最后一段直接取剩下的部分,因此最后一段的下载量大小可能跟前面几段的不同。
然而,从实际验证中可以发现,分段后进行多线程处理可能存在几段线程因阻塞问题而进入等待过程的情况,即这时并不一定是多个线程同时处于下载状态。当出现这种情况时,在等分分段处理后,可能分段1已下载完成,分段2还在线程中等待,从而无法达到快速下载的目的。因此,我们针对该情况进行了优化,即在分段1下载完成时,如果发现分段2还没开始下载,则负责下载分段1的线程继续执行下载任务,即下载分段2,同时移除原先负责下载分段2的线程,从而减轻线程池的阻塞情况。所以优化后,我们的分段处理过程如图2-2所示。
图2-2
以上分段处理过程与等分分段类似,每段的下载起始位置跟等分分段一样,只是每段的下载结束位置都是文件尾部。不过为了防止下载重复内容,下载过程依然需要判断已下载量是否到达等分分段后的下载量。如果到达,则判断下一个分段是否已启动下载,如果还没启动,则移除负责下载下一个分段的线程,并由下载当前分段的线程负责下载,同时更新当前分段的下载量为增加下一个分段的下载量(即当前分段的线程承担了下一个分段的下载任务);如果下一个分段已启动下载,则停止当前分段继续下载。通过这样的优化处理,一是可以解决因线程阻塞导致分段等待下载的问题,二是可以减少发起网络请求(即减少分段线程网络请求)。
2.1.2 常见的下载劫持
下载功能一般都关注下载的成功率、下载速度,如2.1.1节介绍的分段式多线程网络通信技术,其目的就是提高下载速度。但实际中,我们有时候会发现下载下来的文件不是想要的目标文件,这样即使前面的下载技术做得再好也将前功尽弃。因此,下载文件的正确性是最需要关注的问题,下面介绍一下下载劫持。
下载劫持是一个什么概念呢?简单理解就是,你想要下载某个文件A,在你下载完成后却发现得到的是文件B,当然你的下载链接指向的服务器资源文件确实是文件A,其实这就是当你真正下载资源的时候被指向了另一个文件的下载地址,导致下载的文件不是你想要的目标文件,这时即发生了下载劫持。
手机上下载任务的整个链路主要分3部分:手机端、网络层、服务端。这3个部分都可能发生劫持,但常见的劫持主要还是发生在网络层,因为在手机端、服务端进行劫持,需要每个手机端都植入劫持逻辑,劫持成本及难度都比较高,而且容易被发现、清理和追查;而在网络层进行劫持,成本比较低,因为不管是手机端还是服务端,都需要经过网络层进行传输。
对于网络层劫持,可以细分为DNS解析劫持、篡改HTTP请求头和篡改HTTP请求体等方式。
而在网络层的具体劫持实现方式中,最常见的是运营商劫持。接入运营商的互联网设备想要联网,都需要经过运营商(电信、联通等)网关的转接。因此,当我们想要下载目标服务器上的资源文件时,同样需要经过运营商网关的转接,但如果此时运营商解析了我们的请求信息并修改了目标链接地址,那么我们最终会跳转到其他下载地址并下载其他资源文件。
那么既然存在这些下载劫持可能,我们如何做到事前监控、预警呢?需要尽可能地在第一时间发现被劫持了,这就是2.1.3节要讲到的下载劫持监控。
2.1.3 下载劫持监控
在2.1.2节中,我们介绍了下载劫持的危害,因此需要相应的手段来防范它。首先需要知道什么时候会发生下载劫持,即需要进行下载劫持监控。
要想知道是否发生了下载劫持,最重要的就是知道下载的文件是否正确,因此最直接的监控手段就是验证下载的文件,检查其是否正确。在每次下载资源文件前,我们可以提前知道目标文件的大小,因此第一种监控方案就是验证下载后的文件大小是否与预设值一致。
在实际的劫持场景中,几乎所有的下载劫持都是针对App的下载劫持,因此第二种监控方案是,在App下载完成后,解析下载完成的App的包名、版本名,然后验证其是否与预设值一致。
当然,也存在被下载劫持后的文件的大小,App的包名、版本名都与预设值一致的情况,可能只是某个区分渠道来源的标识改变了。这样在下载后,如果用户使用该文件,可能相关的收费统计都会被归于实施劫持的那个渠道。该情况无法通过前面的两种监控方案进行监控。这时我们需要其他监控方案,即最耗时但最准确的监控方案,那就是计算整个文件的MD5值是否发生了改变。关于MD5值,大家可以认为任何文件都有一个唯一对应的MD5值,若某个文件内部某个渠道的标识发生了变化,其实就是该文件发生了变化,则对应的MD5值也就发生了变化。
综上所述,我们可以把几种下载劫持监控用下面的流程图(见图2-3)来表示。
图2-3
2.1.4 在下载中实现反劫持
2.1.3节已讲到如何监控下载劫持,接下来就要介绍非常重要的反劫持处理,因为我们的目标就是下载正确的资源文件。
前面已讲到下载劫持的流程,其中最常见的是网络层的下载劫持,为了防止下载链接在网络层中被劫持替换,最主要的手段就是对请求信息进行加密,即防止被解析替换,因此最有效的方案就是通过HTTPS协议访问资源服务器。
在实际的工作中,用户的下载行为一般发生在Wi-Fi环境下,而且一般都发生在稳定的Wi-Fi环境下,如家里的Wi-Fi环境。如果在某一Wi-Fi环境下发生了下载劫持,则可得出结论,即在该Wi-Fi环境下比较容易发生下载劫持。因此我们可以记录该Wi-Fi环境,如果再在该Wi-Fi环境下下载文件,则直接实施HTTPS反劫持方案,这样可以尽量避免因Wi-Fi环境而发生下载劫持的情况。
2.1.5 下载SDK的应用
下载功能是App中最基本的功能之一,因此下载SDK的应用比较多。例如,每个发布后的App都有自更新升级的业务,其实这就是下载最常见的应用场景。另外,部分App内的下载过程需要提示下载进度,而下载SDK可以实时回调下载进度,因此可以很好地满足该应用场景。对于应用市场这个类型的App,提供App下载服务是其最核心的业务,并且对下载App的正确性要求非常高,因此带有反劫持功能的下载SDK能很好地切合该业务的应用场景。