日常扯蛋
从小到大都是被追求或是互相默许,前两天第一次像个初中生,鼓起勇气的,用微信对她承认了“我的确是喜欢你!”,但她的回答却是。。。
不管怎样,都要谢谢你鼓起勇气跟我说这些,能被人喜欢是件好事情,还蛮开心的,不过我已经有喜欢的人了,他不在XX,分享的像个挺好的,保持呗。我一直都挺看好你的,希望你可以好好加油,一定更适合你的女生。祝君好。
得到这回答的我当然是悲喜交加。在后续的对话中,明白了,她不是完全拒绝我,我还是有希望的。这听起来好像是很犯贱的想法,可是现实就是这样,没多少情侣是一蹴而就的,更多的,是需要努力,一个人,或者两个人的努力。用鸡汤文来说就是:
人生会经历三次成长:
- 第一次是发现自己不是世界中心;
- 第二次是发现即使再怎么努力,终究还是有些事令人无能为力;
- 第三次是在明知道有些事可能会无能为力,但还是会尽力争取的时候。
我想我会努力的,就算没有结果,也不会放弃。感情就是这样,只要你喜欢,没有值不值得。;)
正题
项目需求总在变,不变的只有我们苦命的码农 ;(。因为客户天天催促着上线,又要求App
必须要强制升级(公司业务App
,不是对外的,所以和我码农底线无关),我原本打算封装一个下载管理器,来负责这个项目的APK
下载,但由于时间问题,没有时间再设计、测试,所以只能宣布夭折,然后针对当前这个项目,写一个DownloadService
下载。但客户又说要能够做到断点续传、联网自动继续下载(App
打开情况下)、下载完成后没安装,退出App再打开,没有联网的情况下继续弹出安装的界面……(听到这些需求的我当时已经崩溃),花了两三天,赶出来测试、上线,到下载版本推送的时候,就尴尬了,由于下载网络的问题,各种错误,重复添加下载任务、下载无进度…………经理也无奈了,因为他也测试了这些case,为什么没有发现问题。。。。那天加班到凌晨3点,总算解决,但只是用临时方法,不能长久这样。所以我回头又想起这个之前我打算封装的模块DownloadManager
。有人问,为什么不用系统自带的DownloadManager
,我想说的是“对啊,为啥,我也不知道,就是不想用,就是偏执于自己造轮子(好吧,主要就是系统的这玩意儿你不好控制)”。就趁这段时间闲着,开艹代码吧。
开源库
开源库肯定是要用到的,大路不走偏挑山路,不是浪费生命吗。但不是直接找一个开源的DownloadManager
,因为你自己完全有能力写一个,而且也不难(Glide、Rxjava什么的就算了,那个有点难,还是不自己造轮子了)。我们主要用到两个:
- OkHttp3
- GreenDao 3.1
- Gson
OkHttp3
就不用说了吧,当今主流,主要就是解决网络请求问题。个人感觉,这玩意儿会火,不是因为入门简单,比它简单的有Volley
,那为什么呢?我觉得应该就是定制性。可定制极高,接口设计很精髓,属于诗歌级别的代码。
GreenDao
负责下载任务的DB操作。它倒是没有OkHttp
那么出名。但这不是问题,它的实力摆在那里,这里就不吹嘘它了,仁者见仁智者见智。
Gson
不多说,大家都懂。好吧,这个管理器其实可以不用这玩意儿(也就一个地方用到,而且还是无关紧要),但是,我还是偷懒了,有时间再去掉吧。
可能有的人会说,“为什么不用Retrofit2
、RxJava
等开源库呢?”。其实吧,我在第一个版本里就是用了这些库,但在后面改善、优化的时候意识到,我们应该尽量在封装的库里,少使用第三方的其它库,可以尽量减少耦合度,减少后续维护的成本。
基本流程图
(明天补上,下载Visio中)
开始网络请求
首先我们先构建个负责构建Call
的NetKit.java
。
/**
* Created by mid1529 on 2016/12/23.
* 网络请求
*/
public class NetKit {
private static final long TIMEOUT = 100000;
private static NetKit mNetKit = null;
private static OkHttpClient mClient = null;
private NetKit() {
initClient();
}
/**
* 单例模式,保证唯一
*
* @return Netkit
*/
public static NetKit getKit() {
if (mNetKit == null) {
synchronized (NetKit.class) {
if (null == mNetKit) {
mNetKit = new NetKit();
}
}
}
return mNetKit;
}
/**
* 得到OkhttpClient实例
*
* @return Client
*/
private OkHttpClient initClient() {
if (mClient == null) {
mClient = new OkHttpClient.Builder()
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body())).build();
}
})
.build();
}
return mClient;
}
/**
* 获取下载实例Call
*
* @param url 网址
* @param headers 头,可为空
* @return Call
*/
public synchronized Call get(@NonNull String url, @Nullable Headers headers) {
Request.Builder builder = new Request.Builder()
.url(url)
.get();
if (headers != null) {
builder.headers(headers);
}
return mClient.newCall(builder.build()).clone();
}
}
注意get(@NonNull String url, @Nullable Headers headers)
这个方法,return
的是Call
,所以这里只是构建出Call
,而不是真正的请求。然后下载通常是用Get
的方式,所以用Get
,参数第一个代表URL
,第二个么,代表请求头,因为考虑到,有的下载服务器可能需要头验证,所以预留,但这里已经通过注解声明了@Nullable
,所以可以直接传null
。
建立下载任务实体
下载任务实体命名就俗套一点,叫做DownloadTask
。但在这里,我在看其它开源的下载的时候,发现有的把下载文件读写操作等写在了这实体里,执行时,相当于下载任务本身完成了自己本身的下载任务,虽然没毛病,但总感觉怪怪的,所以我这里觉得,实体只是记录信息,而不包括下载、读写等。废话不说,看代码DownloadTask.java
@Entity //GreenDao的注解,下面也是,不懂的可以Google了解
public class DownloadTask {
@Id(autoincrement = true)
private Long taskId; //下载的Id,数据库自动生成
@Unique
@NotNull
private String url; //下载的URL
@Unique
@NotNull
private String fileName; //下载的文件名
@NotNull
private boolean isAutoContinue = false;
@Convert(converter = DownloadPropertyConverter.class, columnType = String.class) //参数转换器,将Map对象转为`String`储存,查询时,又把String转为Map
private Map<String, String> headers = null; //请求时的Header
@Transient
private long contentLength; //下载文件的总长度
@Transient
private long downloadedLength; //下载文件已下载长度
@Transient
private boolean isResumeBrokenTransfer = true; //是否开启断点续传,默认开启
@Transient
private boolean isPause = false; //是否为暂停,默认为false,防止暂停后,会走onFaild回调方法
@Transient
private Call call = null;
@Transient
private Object tag; //预留,参考View的Tag
public String downloadPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/"; //下载目录
@Transient
private File file = null;
//****省略各种getter、setter
}
转化器DownloadPropertyConverter.java
/**
* Created by mid1529 on 2016/12/26.
* 保留下载任务持久化 时的Headers数据的Converter
* 唯一用到Gson的地方,哈哈哈哈
*/
public class DownloadPropertyConverter implements PropertyConverter<Map<String, String>, String> {
@Override
public Map<String, String> convertToEntityProperty(String databaseValue) {
return new Gson().fromJson(databaseValue, new TypeToken<Map<String, String>>() {
}.getType());
}
@Override
public String convertToDatabaseValue(Map<String, String> entityProperty) {
return new Gson().toJson(entityProperty);
}
}
先写到这,过两天继续
明天继续DownloadService
和负责IO
读写的东西等,好累。