文章目录
- 14.4 总结
- 14.5总结
- 1. gson
- 1.1 gson基本用法
- 1.2 属性重命名
- 1.3 JsonDeserializer
- 1.4 对象嵌套
- 1.5 使用JsonDeserializer解析和风天气数据
- 1.6 使用JsonDeserializer解析其他类型数据
- 2. sharedPreferences
- 2.1 基本操作
- 3.Glide库
- 14.6总结
- 1 下拉刷新
- 2 滑动窗口
- 14.7总结
- 1 android异步处理信息
- 1.1 handler
- 1.2 AsyncTask
- 2.服务
- 3.《第一行代码中的下载示例》
- 1. 回调接口
- 2. DownloadTask
- 2.1 泛型参数
- 2.2 数据域
- 2.3 doInbackground()方法
- 2.4 onProgressUpdate()方法
- 2.5 onPostExecute()方法
- 2.6 通用函数
- 3. DownloadService
- 3.1 通知函数
- 3.2 DownloadListener匿名类
- 3.3 DownloadBinder
- 4. MainAcitivity
- 4.1 服务的绑定
- 4.2 打开时授权
- 4.3 button的点击函数
- 5.结果
14.4 总结
1.点击ADM不能查看data文件夹,android device monitor不能查看/data目录。
2.不要忘记china后面是有/。
1
2String address="http://guolin.tech/api/china/"+provincecode+"/"+citycode;
3.ProgressDialog在24以上的是用不了的。
4.setselection(int position)的作用就是把第position位置的数据显示在listview的最上面一项。来自ListView的setSelection用法。下文代码就是把第一项放在最上面一项。假如你设置1,就是把第二位的数据显示在listview的最上面一项,那么第一项呢,向上滑动就可以看到。
1
2listView.setSelection(0);
5.OkHttp 建立一个新方法,使程序可以多次调用,并带有回调参数callback,同时在client.newCall(request)没有调用excuter()方法,而是调用enqueue()方法,该方法的内部已经开好子线程了,在子线程中去执行HTTP请求,并将最终的请求结果回调OKhttp.Callback当中,在调用它的时候要重写onResponse(),得到服务器返回的具体内容,重写onFailure()方法,在这里对异常情况进行处理。不可以再这里进行UI操作。
1
2
3
4
5
6
7public class HttpUtil { public static void sendOkHttpRequest(String address,okhttp3.Callback callback){ OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder().url(address).build(); client.newCall(request).enqueue(callback); }
那么我们在调用sendOkHttpRequest()方法的时候就可以这样写
1
2
3
4
5
6
7
8
9
10
11
12
13HttpUtil。sendOkHttpRequest("http://www.baidu.com",new okhhtp3.Callback()) { @Override public void onResponse(Call call,Response response)throws IOException{ //得到服务器返回的具体内容 String responseData =response.body().string(); } @Override public void onFailure(Call call,IOException e){ //在这里对异常情况进行处理 } }
在coolweather中,重写了onFailure()方法,onResponse()方法。
-
onFailure()方法
如果加载失败,要返回UI线程,去关闭加载框,并提示加载失败字样。
-
onResponse()方法
首先去判断要去服务器查询什么type的数据,包含三个种类,去调用相应解析json的方法。之后数据库中已经包含数据了,所以调用对应type的读取数据库数据的方法。
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
51HttpUtil.sendOkHttpRequest(address, new Callback() { @Override public void onFailure(Call call, IOException e) { //失败了,返回UI线程处理逻辑 getActivity().runOnUiThread(new Runnable() { @Override public void run() { //关闭加载框 // closeProgressBar(); closeProgressDialog(); Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show(); } }); } //得到服务器返回的具体内容 @Override public void onResponse(Call call, Response response) throws IOException { String responseText=response.body().string(); boolean result=false; if("province".equals(type)) result = Utility.handleProvinceResponse(responseText); else if("city".equals(type)){ result=Utility.handleCityResponse(responseText,selectedProvince.getId()); } else if("county".equals(type)){ result=Utility.handleCountyResponse(responseText,selectedCity.getId()); } //解析完数据牵涉到UI操作,因此必须要在主线程中调用。 //数据库中有了,可以显示了。 if(result){ getActivity().runOnUiThread(new Runnable() { @Override public void run() { ///关闭进度框 // closeProgressBar(); closeProgressDialog(); if("province".equals(type)) queryProvinces(); else if("city".equals(type)){ queryCities(); } else if("county".equals(type)){ queryCounties(); } } }); } } });
- 和风天气默认让你去使用sdk类型的key,所以要注意我们使用的key要是Web API类型,刚开始我注册后,一直不好使,最后才发现申请的key是sdk类型。和风天气注册地址。
14.5总结
1. gson
1.1 gson基本用法
回顾gson的使用,并找一些json格式的数据进行解析。
1
2
3
4String response="[{img=0.0,name=非会员,qualify_amount=0.0}," + "{img=1.0,name=一级会员,qualify_amount=100.0}," + "{img=2.0, name=二级会员,qualify_amount=300.0}]";
由于是数组中有多个对象,所以可以用List的方法进行解析,解析方法如下:
1
2
3
4
5
6Gson gson=new Gson(); //数组的话可以这么做 List<User>users=gson.fromJson(response,new TypeToken<List<User>>(){}.getType()); for (User user:users){ System.out.println(user.img+" "+user.name+" "+user.qualify_amount);
1.2 属性重命名
这里参考自Gson用法详解。当我们的接口返回的数据与我们期望的数据不同时,我们可以使用属性重命名。或者下述的json数据中有一个tz,我们怕忘记它的意思想给他改成time,就需要使用@SerializedName 来建立与java字段的联系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14/** * admin_area : 北京 * tz : +8.00 * location : 北京 * lon : 116.4052887 * parent_city : 北京 * cnty : 中国 * lat : 39.90498734 * cid : CN101010100 */ private String admin_area; @SerializedName("tz") private String time;
SerializeName远比你想象的更强大,假如,json数据如下:userName,user_name,Name,都是指向一个值,此时我们可以使用alternate属性。很奇怪的是,我翻看了我的SerializedName类发现竟然没有alternate这个方法,黑人问号脸,为什么问号它围绕着我?
1
2
3
4
5
6
7
8{"userName":"leavesC","age":24,"sex":true} {"user_name":"leavesC","age":24,"sex":true} {"Name":"leavesC","age":24,"sex":true} @SerializedName(value = "userName", alternate = {"user_name", "Name"}) private String name;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.google.gson.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface SerializedName { String value(); }
1.3 JsonDeserializer
参考自Gson 解析复杂数据。首先有一个Json对象,表示一本书的基本信息。
1
2
3
4
5
6
7{ 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn-10': '032133678X', 'isbn-13': '978-0321336781', 'authors': ['Joshua Bloch', 'Neal Gafter'] }
java的变量名不容许有‘-’符号的,我们可以使用@SerializedName注解来处理,不过对于一些复杂情况是难以处理的,所以我们使用JsonDeserializer。我们需要自定义一个JsonDeserializer,然后注册到GsonBuilder,GsonBuilder在解析时就会自动使用我们自定义的JsonDeserializer。
首先我们需要创建一个BookJsonDeserializer。它负责把Book对应的Json对象,解析为Java对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class BookDeserializer implements JsonDeserializer<Book> { @Override public Book deserialize(final JsonElement jsonElement, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { //todo 解析字段 final Book book = new Book(); book.setTitle(title); book.setIsbn10(isbn10); book.setIsbn13(isbn13); book.setAuthors(authors); return book; } }
简单介绍下JsonDeserializer,它需要有一个Type,那肯定是我们的Book。我们要重写deserialize()函数,并在其中解析json数据,最后返回一个Book对象。这里比较重要的是,Gson解析Json对象在内部表示为JsonElement,虽然element是元素的意思,但JsonElement可以表示为下列的任何一种:
1. JsonPrimitive :Java 基本类型的包装类,以及 String
2. JsonObject:类比 Js 中 Object的表示,或者 Java 中的 Map<String, JsonElement>,一个键值对结构。
3. JsonArray:JsonElement 组成的数组,注意:这里是 JsonElement,说明这个数组是混合类型的。
4. JsonNull:值为 null
完整代码如下,根据上述所讲,就可以解释,为什么要用getAsString和getAsJsonArray了。使用getAsString时是这个JsonElement表示为JsonPrimitive。使用getAsJsonArray时,虽然读取的是[‘Joshua Bloch’, ‘Neal Gafter’]这个数组,但表示为JsonElement所以,要给他转化一下。
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
30class BookDeserializer implements JsonDeserializer<Book>{ @Override public Book deserialize(final JsonElement jsonElement, final Type typeOfT, final JsonDeserializationContext context)throws JsonParseException{ final JsonObject jsonObject=jsonElement.getAsJsonObject(); JsonElement titleElement=jsonObject.get("title"); final String title=titleElement.getAsString(); JsonElement isbn10Element=jsonObject.get("isbn-10"); final String isbn10=isbn10Element.getAsString(); JsonElement isbn13Element=jsonObject.get("isbn-13"); final String isbn13=isbn13Element.getAsString(); //虽然是个数组,但Gson解析json对象并在内部表示为JsonElement, // 这里的其实是表示为JsonElement的JsonArray,所以要在get之后用getAsJsonArray(); JsonArray authors=jsonObject.get("authors").getAsJsonArray(); String[]Authors=new String[authors.size()]; //Array中存放的是element int i=0; for(JsonElement jsonElement1:authors){ Authors[i++]=jsonElement1.getAsString(); } //todo 解析字段 final Book book=new Book(); book.setTitle(title); book.setIsbn10(isbn10); book.setIsbn13(isbn13); book.setAuthors(Authors); return book; }
这里要注意一下,Book类我们要重写一下toString(),否则的话无法得到我们想要的结果,直接System.out.println(book)就会输出book对应的地址。我们的检验代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static void main(String[]args){ String response="{"title":"Java Puzzlers: Traps, Pitfalls, and Corner Cases"," + ""isbn-10":"032133678X"," + ""isbn-13":"978-0321336781"," + ""authors":["Joshua Bloch"," + ""Neal Gafter"]}"; //使用GsonBuilder来注册BookDeserializer,并创建Gson对象,这样的话, // 需要解析Book的时候,就会使用BookDeserializer来解析 GsonBuilder gsonBuilder=new GsonBuilder(); gsonBuilder.registerTypeAdapter(Book.class,new BookDeserializer()); Gson gson=gsonBuilder.create(); Book book = gson.fromJson(response, Book.class); System.out.println(book); }
最后是解析的过程:
1.将输入的字符串解析为JsonElement
2.找到对应的JsonDeserializer来解析,就找到我们的BookDeserializer
3.传入参数进deserialize()函数,讲JsonElement转换成Book对象
4.将Book对象返回fromJson()
1.4 对象嵌套
假如数据的结构如下,大类是Book类,其中还包括Author类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{ 'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases', 'isbn': '032133678X', 'authors':[ { 'id': 1, 'name': 'Joshua Bloch' }, { 'id': 2, 'name': 'Neal Gafter' } ] }
1
2
3
4
5public class Author{ long id; String name; }
我们有三种方法去解析这个Author类,
1.我们更新BookDeserializer,同时在其中解析authors字段。
2.可以使用默认的Gson实现,因为Author类的数据域和author的Json是一一对应的。
3.新写一个AuthorDeserializer来处理author的解析问题。
第二种方式:JsonDeserializer 的 deserialize() 方法提供了一个 JsonDeserializationContext 对象,这个对象基于 Gson 的默认机制, 我们可以选择性的将某些对象的反序列化工作委托给这个JsonDeserializationContext。JsonDeserializationContext 会解析 JsonElement 并返回对应的对象实例,并在BookDserializer中set一下值即可。
1
2
3Author author = jsonDeserializationContext.deserialize(jsonElement, Author.class); Book.setauthor(author);
第三种方式:新写一个AuthorDeserializer,这个类很简单,就不放代码了,要记得在主函数中,给GsonBuilder注册。
1
2
3
4
5
6// Configure GSON final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer()); gsonBuilder.registerTypeAdapter(Author.class, new AuthorDeserializer()); final Gson gson = gsonBuilder.create();
1.5 使用JsonDeserializer解析和风天气数据
首先要说明JSONObject和JsonObject是不同的,前者是json类下的,后者是gson类下的,我们尝试全部使用gson来解析和风天气数据。下面是json数据类型。其实解析起来很简单,我们定义三个类,分别是basic,update,now,三个类。这里主要就是想练习一下JsonDeserializer。
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{ HeWeather6: [ { basic: { cid: "CN101010100", location: "北京", parent_city: "北京", admin_area: "北京", cnty: "中国", lat: "39.90498734", lon: "116.4052887", tz: "+8.00" }, update: { loc: "2019-03-21 15:55", utc: "2019-03-21 07:55" }, status: "ok", now: { cloud: "0", cond_code: "100", cond_txt: "晴", fl: "8", hum: "11", pcpn: "0.0", pres: "1021", tmp: "12", vis: "16", wind_deg: "3", wind_dir: "北风", wind_sc: "2", wind_spd: "11" } } ] }
我们定义的WeatherDeserializer定义如下,主要就是练习对象与数组的转换,以及context的默认方法。注意的就是可以使用gsonformat插件来生成类,非常的便捷。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class WeatherDeserializer implements JsonDeserializer<Weather>{ @Override public Weather deserialize(final JsonElement jsonElement, final Type typeOfT, final JsonDeserializationContext context)throws JsonParseException { final JsonObject jsonObject = jsonElement.getAsJsonObject(); final JsonArray jsonArray=jsonObject.get("HeWeather6").getAsJsonArray(); final JsonObject jsonObject1=jsonArray.get(0).getAsJsonObject(); Basic basic=context.deserialize(jsonObject1.get("basic"), Basic.class); Update update=context.deserialize(jsonObject1.get("update"),Update.class); String status=context.deserialize(jsonObject1.get("status"),String.class); Now now=context.deserialize(jsonObject1.get("now"),Now.class); Weather weather=new Weather(); weather.basic=basic; weather.update=update; weather.status=status; weather.now=now; return weather; } }
1.6 使用JsonDeserializer解析其他类型数据
最初想深入学习Gson是由于看了这篇文章,你真的会用Gson么。这篇文章有一个评论,有人问这个json形式数据该如何解析。我打算解析下这个数据练下手。
1
2
3
4{0={img=0.0,name=非会员,qualify_amount=0.0}," + "1={img=1.0,name=一级会员,qualify_amount=100.0}," + "2={img=2.0, name=二级会员,qualify_amount=300.0}}
我们的类定义如下,数据存储形式为map的键值对类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class User{ Map<Integer,Data> map; class Data { public double img; public String name; public double qualifty_amount; @Override public String toString() { return "Data{" + "img=" + img + ", name='" + name + ''' + ", qualifty_amount=" + qualifty_amount + '}'; } }
定义一个MapDeserializer来方便我们解析json数据。首先我们的数据是一个object,所以要将其变为JsonObject,最初的想法是去get(),但这里数据量较小,只有0,1,2,但真实数据一定很大,我看了一下JsonObject的函数,惊喜的发现有entrySet()方法,我们利用这个方法就可以实现所有数据的遍历,也节省了代码量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class MapDeserializer implements JsonDeserializer<User> { @Override public User deserialize(final JsonElement jsonElement, final Type typeOfT, final JsonDeserializationContext context)throws JsonParseException { JsonObject jsonObject=jsonElement.getAsJsonObject(); Set<Map.Entry<String,JsonElement>> set=jsonObject.entrySet(); Map<Integer, User.Data>map=new HashMap<>(); //User.Data data0=context.deserialize(jsonObject.get("0"),User.Data.class); //map.put(0,data0); for(Map.Entry<String,JsonElement>s:set){ User.Data data=context.deserialize(s.getValue().getAsJsonObject(), User.Data.class); map.put(Integer.valueOf(s.getKey()),data); } User user=new User(); user.map=map; return user; } }
2. sharedPreferences
2.1 基本操作
读取数据:
服务与活动
1
2
3SharedPreferences prefs=PreferenceManager.getDefaultSharedPreferences(this); String times=prefs.getString("times",null);
碎片
1
2
3SharedPreferences prefs= PreferenceManager.getDefaultSharedPreferences(getContext()); String test=prefs.getString("times",null);
写入数据:
在碎片中写入:
1
2
3
4
5
6
7
8SharedPreferences.Editor editor=PreferenceManager. //不能使用getDefaultSharedPreferences(WeatherActivity.this) getDefaultSharedPreferences(getContext()) .edit(); editor.putString("weather_id",weatherid); editor.apply();
在活动或服务中写入:
1
2
3
4
5
6SharedPreferences.Editor editor=PreferenceManager. getDefaultSharedPreferences(AutoUpdateService.this) .edit(); editor.putString("weather",responseText); editor.apply();
1
2
3
4
5SharedPreferences.Editor editor = PreferenceManager. getDefaultSharedPreferences(WeatherActivity.this).edit(); editor.putString("weather", responseText); editor.apply();
3.Glide库
14.6总结
1 下拉刷新
2 滑动窗口
14.7总结
1 android异步处理信息
- 服务是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互且要求长期运行的任务。
- 服务不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程。
- 需要在服务内部手动创建子线程。
1.1 handler
使用handler可以解决在子线程中进行UI操作,首先创建一个Handler对象,重写handleMessage()方法,在这里对具体的Message进行处理,这里可以判断Message的what字段,然后进行操作。同时,在子线程中的run()中创建一个Message对象,并将该对象的what字段指定为指定值,并使用Handler的sendMessage去发送创建的Message对象。这里handleMessage方法在主线程,所以可以使用UI操作。
1
2
3
4
5
6
7
8
9
10
11
12
13public static final int UPDATE_TEXT =1; private Handler handler=new Handler(){ public void handleMessage(Message msg){ switch (msg.what){ case UPDATE_TEXT: text.setText("Nice to meet you"); break; default: break; } } };
1
2
3
4
5
6
7
8
9new Thread(new Runnable() { @Override public void run() { Message message=new Message(); message.what=UPDATE_TEXT; handler.sendMessage(message); } }).start();
Android中的异步消息处理主要包括:Message、Handler、MessageQueue和Looper。
- Message
Message是在线程之间传递的消息,他可以在内部携带少量的信息,用于在不同线程之间交换数据。包括what字段,arg1字段,arg2字段来携带一些整型数据。使用obj字段携带一个Object字段。 - Handler
Handler主要用于发送和处理消息。发送消息是使用Handler的sendMessage()方法(一般在子线程中使用,新建一个Message对象,并设置字段的信息),最终会传递到Handler的handleMessage()方法(一般会新建一个Handler对象,然后重写这个方法)。 - MessageQueue
它是一个消息队列,用于存放所有通过Handler发送的消息。 - Looper
调用Looper的loop()方法后,就会进入到一个无限循环,每当发现MessageQueue中存在一条消息,就将其取出,并传递到Handler的handleMessage()方法中。
整体流程:
首先需要在主线程中创建一个Handler对象,并重写handleMessage()方法。然后当子线程中需要进行UI操作,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息就会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发会Handler的handleMessage()方法中。
1.2 AsyncTask
AsyncTask是一个抽象类,我们在继承时,需要指定3个泛型参数。
- params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
- Progress:后台任务执行时,如果需要在界面上显示当前的进度,使用这里指定的泛型作为进度单位。
- Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
AsyncTask一般需要重写四个函数:
- onPreExecute()
在后台任务开始之前调用,用于一些界面上的初始化操作。 - doInBackground(Params…)
所有代码都会在子线程中执行。在这里处理所有耗时任务,任务一旦完成就可以通过return语句将任务的执行语句返回。但是不可以进行UI操作。可以调用publishProgress(Progress)方法来反馈进度。 - onProgressUpdate(Progress…)
当在后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate(Progress…)方法就会很快调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作。 - onPostExecute(Result)
当后台任务执行完毕并通过return语句返回时,这个方法就会被调用,可以利用返回的数据来进行一些UI操作,比如提醒任务执行的结果。
2.服务
1 服务一般要重写onCreate(),onStartCommand(),onDestroy()这三个方法。同时需要注意服务需要注册。
2 服务要启动都需要使用Intent。
1
2
3Intent startIntent=new Intent(this,MyService.class); startService(startIntent);
3 活动与服务通信,需要使用onBind()方法。
我们在服务中新建一个DownloadBinder类,并在其中写一些方法以便我们使用,然后新建一个该类的对象,在onBind()方法返回这个对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//这是在服务类 private DownloadBinder mBinder=new DownloadBinder(); class DownloadBinder extends Binder{ public void startDownload(){ Log.d("MyService","startDownload executed"); } public int getProgress(){ Log.d("MyService","getProgress executed"); return 0; } } public MyService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return mBinder; }
我们在活动中,调用Binder。我们在活动中创建一个ServiceConnection的匿名类,并在里面重写onServiceConnected()方法和onServiceDisconnected()方法,分别在活动与服务成功绑定以及接触绑定时调用。在onServiceConnected我们通过向下转型得到了DownloadBinder的是咧,之后就可以调用DownloadBinder中任何public()的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//这是在活动中 private MyService.DownloadBinder downloadBinder; private ServiceConnection connection=new ServiceConnection() {; //活动与服务成功绑定时调用 @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder=(MyService.DownloadBinder)service; downloadBinder.startDownload(); downloadBinder.getProgress(); } //活动与服务成功解绑时调用 @Override public void onServiceDisconnected(ComponentName name) { } };
4 使用startForeground()方法可以使通知一直显示,前台服务。
5. 使用IntentService开启新线程。
3.《第一行代码中的下载示例》
1. 回调接口
首先是定义一个回调接口,用于对下载过程中的各种状态进行监听和回调。
1
2
3
4
5
6
7
8
9
10
11
12
13public interface DownLoadListener { //通知当前的下载进度 void onProgress(int progress); //通知下载成功事件 void onSuccess(); //通知下载失败事件 void onFailed(); //通知下载暂时时间 void onPaused(); //通知下载取消事件 void onCanceled(); }
2. DownloadTask
2.1 泛型参数
新写一个DownloadTask完成下载功能.。首先是传入的三个泛型参数,第一个泛型参数(Param)为String,这个是要传入的Url数据,会传入doInbackground()方法。第二个泛型参数(Progress)是进度显示单位,会通过publishProgress()更新,并回调给onProgressUpdate()方法。第三个(Result)是返回的结果的类型,这个参数用于doInbackground()方法返回,以及onPostExcute()方法执行一些操作。
2.2 数据域
首先有四个整形常量。代表了当前下载任务的不同状态。之后有一个我们定义的接口的实例对象,我们要在onPostExcute()方法中根据不同的result来执行该对象的不同方法。最后有两个boolean型变量,因为我们在doInbackground()中要时刻判断是否点击取消或暂停。
2.3 doInbackground()方法
在这个方法要完成文件的下载,以及更新下载的进度。
文件的下载中需要实现断点下载。我们将其存在sd卡的download目录,我们首先要判断文件是否存在,如果存在就要记录已下载文件长度,我们通过okhttp来获取下载文件的长度,判断已下载文件长度是否等于下载文件长度,相等就可以直接返回Type_SUCCESS,否则以已下载长度开始下载。增加断点的方法如下。
1
2
3
4
5
6
7
8OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder() //增加断点 .addHeader("RANGE","bytes="+downloadedLength+"-") .url(downloadUrl) .build(); Response response=client.newCall(request).execute();
接下来通过java的文件流方式,不断从网络上读取数据,并写入本地,同时计算出progress,并使用publishProgress()方法来传递给onProgressUpdate()。在这个过程我们要时刻判断那两个boolean变量是否为true,为true就返回相应的result。正常完成循环后返回TYPE_SUCCESS,如果出现任何错误最终会返回TYPE_FAILED。
2.4 onProgressUpdate()方法
这个方法在publishProgress()后就会回调,虽然一直用progress代替,但传入的是values,从values【0】中取出progress,与一个对象lastProgress进行比较,如果有更改就去调用listener的onProgress()函数。我们在listener的onProgress()中使用新的progress来创建新的通知,就可以达到实时更新下载进度的功能。
1
2
3
4
5
6
7
8@Override protected void onProgressUpdate(Integer...values){ int progress=values[0]; if (progress>lastProgress) listener.onProgress(progress); lastProgress=progress; }
2.5 onPostExecute()方法
这个函数是用来通知下载结果的,由于在doInbackground()不可以进行UI操作,所以需要使用这个方法来通知下载结果,这个函数在doInbackground()返回结果后会调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@Override protected void onPostExecute(Integer status){ switch (status){ case TYPE_CANCELED: listener.onCanceled(); break; case TYPE_PAUSED: listener.onPaused(); break; case TYPE_FAILED: listener.onFailed(); break; case TYPE_SUCCESS: listener.onSuccess(); break; default: break; } }
2.6 通用函数
首先是两个设置函数,会在Activity中按下相应button时调用,将boolean设置为true,就会在doInBackground中暂停下载或取消下载。最后的这个通用函数是使用OkHttp来获取文件的总长度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public void pauseDownload(){ isPaused=true; } public void cancelDownload(){ isCanceled=true; } private long getContentLength(String downloadUrl)throws IOException{ OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder() .url(downloadUrl) .build(); Response response=client.newCall(request).execute(); if (response!=null&&response.isSuccessful()){ long contentLength=response.body().contentLength(); response.close(); return contentLength; } return 0; }
完整代码如下:
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
138public class DownloadTask extends AsyncTask<String,Integer,Integer> { public static final int TYPE_SUCCESS = 0; public static final int TYPE_FAILED = 1; public static final int TYPE_PAUSED = 2; public static final int TYPE_CANCELED = 3; private DownLoadListener listener; private boolean isCanceled=false; private boolean isPaused=false; private int lastProgress; public DownloadTask(DownLoadListener listener){ this.listener = listener; } //在后台做的东西 @Override protected Integer doInBackground(String...params){ InputStream is = null; RandomAccessFile savedFile = null; File file=null; try{ long downloadedLength=0;//记录已下载的文件。 String downloadUrl = params[0]; String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/")); //下载到sd卡的Download目录 String directory= Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS).getPath(); file = new File(directory+fileName); //已经下载了一部分了 if (file.exists()){ downloadedLength=file.length(); } long contentLength=getContentLength(downloadUrl); if (contentLength==0) return TYPE_FAILED; else if(contentLength==downloadedLength) //已下载等于文件总字节,说明下载完毕 return TYPE_SUCCESS; OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder() //增加断点 .addHeader("RANGE","bytes="+downloadedLength+"-") .url(downloadUrl) .build(); Response response=client.newCall(request).execute(); if (response!=null){ is=response.body().byteStream(); savedFile=new RandomAccessFile(file,"rw"); savedFile.seek(downloadedLength); byte[]b=new byte[1024]; int total=0; int len; while((len = is.read(b))!=-1) { //随时判断是否被取消或终止, if (isCanceled){ return TYPE_CANCELED; } else if (isPaused) return TYPE_PAUSED; else { total+=len; savedFile.write(b,0,len); int progress=(int)((total+downloadedLength)*100/contentLength); //通知下载进度变化,之后很快回调用onProgressUpdate()方法 publishProgress(progress); } } response.body().close(); return TYPE_SUCCESS; } }catch (Exception e) {e.printStackTrace();} finally { try { if (is!=null) is.close(); if (savedFile!=null) savedFile.close(); if (isCanceled&&file!=null) file.delete(); }catch (Exception e) { e.printStackTrace(); } } return TYPE_FAILED; } //更新下载数据。在这里去调用listener的onProgress()方法, //listener的onProgress()方法中时刻创建新通知,去更新数据。 @Override protected void onProgressUpdate(Integer...values){ int progress=values[0]; if (progress>lastProgress) listener.onProgress(progress); lastProgress=progress; } //根据参数去回调 @Override protected void onPostExecute(Integer status){ switch (status){ case TYPE_CANCELED: listener.onCanceled(); break; case TYPE_PAUSED: listener.onPaused(); break; case TYPE_FAILED: listener.onFailed(); break; case TYPE_SUCCESS: listener.onSuccess(); break; default: break; } } public void pauseDownload(){ isPaused=true; } public void cancelDownload(){ isCanceled=true; } private long getContentLength(String downloadUrl)throws IOException{ OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder() .url(downloadUrl) .build(); Response response=client.newCall(request).execute(); if (response!=null&&response.isSuccessful()){ long contentLength=response.body().contentLength(); response.close(); return contentLength; } return 0; } }
3. DownloadService
在该类中,我们主要重写了之前定义的回调接口的匿名类;新写了一个DownloadBinder与Activity绑定,最后写了使用通知的函数。
3.1 通知函数
正常的通知是如下去写的。首先要有一个NotificationManger,之后为了防止不同版本的问题,会使用NotificationCompat.Builder(this)来创建,之后包括了不同的set函数。
值得一提的是下面的setstyle是为了完成内容很多的时候不被屏幕挡住,内容完全显示出来的功能。这里使用PendingIntent的作用是当我们点击顶端通知栏可以跳转到其他acitivity。传入四个参数分别是Context,第二个和第四个一般传入0,第三个参数是一个Intent对象,可以通过这个对象构建出PendingIntent的“意图”。也就是跳转到哪个Activity。Intent倾向于立即执行某个操作,而PendingIntent更加倾向于在某个合适的时机去执行某个操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Intent intent=new Intent(this,Main2Activity.class); PendingIntent pi=PendingIntent.getActivity(this,0,intent,0); NotificationManager manager=(NotificationManager)getSystemService (NOTIFICATION_SERVICE); //防止不同版本 Notification notification=new NotificationCompat.Builder(this) .setContentTitle("this is content title") .setContentText("This is content text") //会覆盖text .setStyle(new NotificationCompat.BigTextStyle().bigText ("abcdefgauwohewqkehwqkehwqkewkqejqwkejqwkejwqkekwqehwqke" + "hwqkehqwkehwqweqwewqeqwewqewq")) .setPriority(NotificationCompat.PRIORITY_MAX) .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(pi) //不能是id。要求是传入 bitmap。 .setLargeIcon(BitmapFactory.decodeResource(getResources() ,R.mipmap.ic_launcher)) .setAutoCancel(true) .build(); manager.notify(1,notification);
我们在这个类中将其分开成两个函数,方便我们去多次调用。第一个函数没什么说的,就是去获取一个NotificationManger,第二个函数有一个跳转。跳转回主活动。这里有一个setProgress函数,第一个参数传入通知的最大进度,第二个参数传入通知的当前进度,第三个参数表示是否使用模糊进度条。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private NotificationManager getNotificationManager(){ return (NotificationManager)getSystemService(NOTIFICATION_SERVICE); } private Notification getNotification(String title,int progress){ Intent intent=new Intent(this,MainActivity.class); PendingIntent pi= PendingIntent.getActivity(this,0,intent,0); NotificationCompat.Builder builder=new NotificationCompat.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); builder.setContentIntent(pi); builder.setContentTitle(title); if (progress>0){ //当progress大于或等于0时才显示下载进度。 builder.setContentText(progress+"%"); //第一个参数传入通知的最大进度,第二个为当前进度,第三个为是否使用 //模糊进度条。 builder.setProgress(100,progress,false); } return builder.build(); }
3.2 DownloadListener匿名类
主要重写了以下几个函数。
- onProgress
重新建立一个通知,也就是在doInbackground中进度更改后,使用publishProgress()去告诉progress更改,此时onProgressUpdate()就会调用,确定progress比lastProgress大后,就会去调用listener的onProgress方法,就会重新建立一个通知,达到时刻去更新通知中的进度数值。 - onSuccess
回调这函数,代表下载完成,所以要关闭我们的downloadTask,停止前台服务通知,创建一个下载成功的通知,然后使用Toast弹窗来告诉用户下载完成。 - onFailed
回调这个函数,代表下载失败,和onSuccess相似。 - onPaused
与onSuccess相似,只不过不用停止前台服务通知,因为它是暂停。 - onCanceled
与onSuccess相似。
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
46private DownLoadListener listener=new DownLoadListener() { @Override public void onProgress(int progress) { //建立一个通知 getNotificationManager().notify(1,getNotification("Downloading...", progress)); } @Override public void onSuccess() { downloadTask=null; //下载成功时将前台服务通知关闭,并创建一个下载成功的通知 stopForeground(true); getNotificationManager().notify(1,getNotification("Download Success", -1)); Toast.makeText(DownloadService.this,"Download Success", Toast.LENGTH_SHORT).show(); } @Override public void onFailed() { downloadTask=null; //下载失败时将前台服务通知关闭,并创建一个下载失败的通知 stopForeground(true); getNotificationManager().notify(1,getNotification("Download Failed", -1)); Toast.makeText(DownloadService.this,"Download Failed", Toast.LENGTH_SHORT).show(); } @Override public void onPaused() { downloadTask=null; Toast.makeText(DownloadService.this,"Paused",Toast.LENGTH_SHORT) .show(); } @Override public void onCanceled() { downloadTask=null; stopForeground(true); Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT) .show(); } };
3.3 DownloadBinder
我们通过自定义DownloadBinder类使活动可以与服务通信。这里包含三个方法,分别用于开始下载,暂停下载和取消下载的。
- startDownload
在这个方法中,我们创建了一个DownloadTask的实例,然后调用execute方法开启下载,execute方法传入的是下载url地址,也是三个泛型参数的第一个参数Params。同时开启前台服务,和Toast提示用户。 - pauseDownload
这个方法比较简单就是调用了DownloadTask对象的pauseDownload方法。回忆这个方法的流程,就是将boolean型变量isPause变成true,这样就在InBackGround中返回一个Type_Pause,就会在onPostExecute中去回调DownloadListener的onPause()方法。 - cancelDownload
和上一个方法类似,但我们不光要将boolean型变量设置为true,还需要去删除掉已经下载了的文件。
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
47private DownloadBinder mBinder=new DownloadBinder(); @Override public IBinder onBind(Intent intent) { return mBinder; } //服务与活动通信 class DownloadBinder extends Binder{ public void startDownload(String url){ if (downloadTask==null) { downloadUrl=url; downloadTask=new DownloadTask(listener); //execute传入AsyncTask<Params, Progress, Result>的第一个参数。 downloadTask.execute(downloadUrl); startForeground(1,getNotification("Downloading...",0)); Toast.makeText(DownloadService.this,"Downloading...", Toast.LENGTH_SHORT).show(); } } public void pauseDownload(){ if (downloadTask!=null) { downloadTask.pauseDownload(); } } public void cancelDownload(){ if(downloadTask!=null) downloadTask.cancelDownload(); else{ if (downloadUrl!=null){ //取消下载时需将文件删除 String fileName=downloadUrl.substring( downloadUrl.lastIndexOf("/")); String directory = Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS).getPath(); File file=new File(directory+fileName); if (file.exists()) file.delete(); getNotificationManager().cancel(1); stopForeground(true); Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();; } } } }
4. MainAcitivity
在主活动中,我们要实现服务的绑定,app的打开时授权,button的点击函数。layout文件很简单就是包含三个button:分别是startdownload,canceldownload,pausedownload。
4.1 服务的绑定
我们首先创建了ServiceConnection的匿名类,并在onServiceConnected方法中获取到DownloadBinder的实例,这样在与服务连接上的时候就可以调用服务中提供的方法了(startDownload,cancelDownload和pauseDownload)。调用startService和bindService方法来启动和绑定服务,启动服务就可以保证DownloadService一直在后台运行,绑定服务可以让MainActivity和DownloadService进行通信。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private DownloadService.DownloadBinder downloadBinder; private ServiceConnection connection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder=(DownloadService.DownloadBinder)service; } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { ... Intent intent=new Intent(this,DownloadService.class); startService(intent); //使活动与服务可以通信 bindService(intent,connection,BIND_AUTO_CREATE); }
4.2 打开时授权
我们在onCreate函数中,使用checkSelfPermiison来判断是否被许可使用权限。未被允许的话,就会调用requestPermissions方法,我们想要被用户允许的权限要放在一个String数组里,最后的1就是随便传入的一个id值。
1
2
3
4
5
6
7
8
9
10
11@Override protected void onCreate(Bundle savedInstanceState) { ... if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest. permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } }
当弹窗出现提示用户是否允许后,不管结果如何,都会回调这个onRequestPemissionsResult()方法,首先判断的是requestCode,这个就是刚刚的那个id1,我们的结果存储在grantResults数组中。我们要在不允许的情况下,弹出一个Toast提示用户。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//用户选择是否接受权限后回调的函数 @Override public void onRequestPermissionsResult(int requestCode,String[]permissions, int[]grantResults) { switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] != PackageManager. PERMISSION_GRANTED) { Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT) .show(); finish(); } break; default: } }
4.3 button的点击函数
这里要说明的我们不能为每个button去写他的clickLinstener,这样很麻烦,我们可以使用实现View.OnClickListener接口。
1
2public class MainActivity extends AppCompatActivity implements View.OnClickListener
在onCreate中我们直接setOnclickLinstener(this)即可。
1
2
3
4
5
6
7
8
9
10
11
12@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button startDownload=(Button)findViewById(R.id.start_download); Button pauseDownload=(Button)findViewById(R.id.pause_download); Button cancelDownload=(Button)findViewById(R.id.cancel_download); startDownload.setOnClickListener(this); pauseDownload.setOnClickListener(this); cancelDownload.setOnClickListener(this); }
在onClick中我们使用v.getId()去判断究竟是哪个button即可。根据不同button我们去使用服务(DownloadBinder实例)提供的不同方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19@Override public void onClick(View v){ if (downloadBinder==null) return; switch (v.getId()){ case R.id.start_download: String url="https://raw.githubusercontent.com/guolindev/eclipse/master" + "/eclipse-inst-win64.exe"; downloadBinder.startDownload(url); break; case R.id.pause_download: downloadBinder.pauseDownload(); break; case R.id.cancel_download: downloadBinder.cancelDownload();; break; } }
5.结果
最后运行效果如下:最初是一个运行时权限,之后可以看到,点击开始后可以开始下载,点击暂停后再次点击开始会从之前暂停处继续下载,而点击取消下载后再次点击开始下载会从0开始,说明文件已被删除。
最后
以上就是紧张砖头最近收集整理的关于《Android第一行代码》coolweather项目个人总结14.4 总结14.5总结14.6总结14.7总结的全部内容,更多相关《Android第一行代码》coolweather项目个人总结14.4内容请搜索靠谱客的其他文章。
发表评论 取消回复