网络技术
WebView
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView=findViewById(R.id.web_view);
//调用WebView的getSetting()方法设置一些浏览器的属性
//调用setJavaScriptEnabled()方法来让WebView支持JavaScript脚本
webView.getSettings().setJavaScriptEnabled(true);
//当需要从一个网页跳转到另一个网页时,我们希望目标网页仍然在当前WebView中显示,
//而不是打开浏览器
webView.setWebViewClient(new WebViewClient());
//将网址传入,即可展示相应的网页内容
webView.loadUrl("http:www.taobao.com");
}
使用到网络功能,需要声明权限
<uses-permission android:name="android.permission.INTERNET"/>
网络编程部分最核心两块内容----发起Http请求数据(OkHttp)和解析数据(GSON)
HTTP协议
工作原理:客户端向服务器发送一条HTTP请求,服务器收到请求之后会自动返回一些数据给客户端,然后客户端再对这些数据进行解析和处理.
比如说上面的WebView控件,就是我们向淘宝的服务器发送了一条HTTP请求,接着服务器分析出我们想要访问的是淘宝的首页,于是会把该网页的HTML代码进行返回,然后WebView再调用手机浏览器的内核对返回的HTML代码进行解析,最终将页面展示出来.
我们通过HttpURLConnection来体会发送HTTP请求的过程,深入理解一下这个过程.
使用HttpURLConnection
- 1.首先需要获取到HttpURLConnection的实例,需要提前new出一个URL对象,并传入目标网址,然后再调用openConnection()方法即可
- 2.得到HttpURLConnection实例之后,设置HTTP请求所使用的的方法.常用方法有两个GET和POST.前者表示希望从服务器那里获取数据,而后者表示希望提交数据给服务器
- 3.调用getInputStream()方法获取服务器返回的输入流,对获取到的服务器返回的输入流进行读取
- 4.最后调用HttpURLConnection的disconnect()方法将HTTP连接关掉
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button mBtnSendrequest;
private TextView mTvResponse;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnSendrequest = findViewById(R.id.send_request);
mTvResponse = findViewById(R.id.response_text);
mBtnSendrequest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.send_request:
//逻辑代码
sendRequestWithHttpURLConnection();
break;
}
}
private void sendRequestWithHttpURLConnection() {
//开启线程发送网络请求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection httpURLConnection = null;
BufferedReader bufferedReader = null;
try {
//首先需要获取到HttpURLConnection的实例
//需要提前new出一个URL对象,并传入目标网址,这里传入的是百度的网址
//然后再调用openConnection()方法即可
URL url = new URL("https://www.baidu.com");
httpURLConnection = (HttpURLConnection) url.openConnection();
//得到HttpURLConnection实例之后,设置HTTP请求所使用的的方法
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setConnectTimeout(8000);
httpURLConnection.setReadTimeout(8000);
//调用getInputStream()方法获取服务器返回的输入流
InputStream inputStream = httpURLConnection.getInputStream();
//下面对获取到的服务器返回的输入流进行读取
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//下面将读取到的字符串进行拼接
StringBuilder response = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
response.append(line);
}
showResponse(response.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpURLConnection != null) {
//最后调用HttpURLConnection的disconnect()方法将HTTP连接关掉
httpURLConnection.disconnect();
}
}
}
}).start();
}
//调用一个runOnUiThread()方法,在这个方法的匿名类参数中进行操作将返回的数据显示到界面上
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在这里执行UI操作,将结果显示到界面上
mTvResponse.setText(response);
}
});
}
}
<uses-permission android:name="android.permission.INTERNET"/>
如果想要提交数据给服务器,只需要将HTTP请求的方法改为POST,并在获取输入流之前把要提交的数据写出即可.注意每条服务器都要以键值对的形式存在,数据和数据之间用"&"符号隔开
httpURLConnection.setRequestMethod("POST");
DataOutputStream outputStream=new DataOutputStream(httpURLConnection.getOutputStream());
outputStream.writeBytes("username=aimin&password=123456");
使用OkHttp(重点掌握!!!!网络请求的重要框架!!!首选的网络通信库)
https://github.com/square/okhttp
1.首先需要添加库的依赖
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
2.OkHttp的具体用法:
如果想从服务器那里获取数据
- 1.首先要创建一个OkHttpClient的实例
- 2.如果想要发起一条Http请求,就需要创建一个request对象然后采用连缀方法丰富这个request对象
- 3.调用OkHttpClient的newCall()方法来创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据
private void sendGetRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//首先创建一个OkHttpClient实例,Client是代理人的意思
OkHttpClient client = new OkHttpClient();
//创建一个request对象来发起一条HTTP请求
Request request = new Request.Builder()
//指定访问的服务器是淘宝
.url("https://www.taobao.com").build();
//创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据
//其中Response对象就是服务器返回的数据
Response response = client.newCall(request).execute();
String responseData = response.body().string();
//parseXMLWithPull(response);
showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
如果希望提交数据给服务器
- 1.首先要创建一个OkHttpClient的实例
- 2.构建出一个RequestBody对象来存放待提交的参数
- 3.在Request.Builder中调用一下post()方法,并将RequestBody对象传入
- 4.调用execute()方法来发送请求并获取服务器返回的数据即可
private void sendPostRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//首先创建一个OkHttpClient实例,Client是代理人的意思
OkHttpClient client = new OkHttpClient();
//创建一个RequestBody对象来存放待提交的参数
RequestBody requestBody=new FormBody().Builder()
.add("username","admin")
.add("password","123456")
.build();
//在Request.Builder中调用一下post方法,并将RequestBody对象传入
Request request=new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
//创建一个Call对象,并调用Call对象的execute()方法来发送请求并获取服务器返回的数据
//其中Response对象就是服务器返回的数据
Response response = client.newCall(request).execute();
String responseData = response.body().string();
//parseXMLWithPull(response);
showResponse(responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
解析网络格式数据
每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以从服务器上获取数据.这些数据一般有自己的结构规格和语义,都是经过格式化的数据;当我们在网络上上传了这些数据之后,另一方收到这些数据消息之后就可以按照相同的结构规格进行解析,从而取出他想要的那部分内容.
网络传输数据时最常用的格式有两种:XML和JSON
XML和JSON的对比:XML的语义性比JSON要好,但是JSON的优势在于它的体积小,在网络上传输的时候可以更省流量.(所以通常情况下我们都会选择JSON格式来进行网络传输数据)
解析XML格式数据
首先搭建一个简单的Web服务器(这里我们选择Apache型服务器),在服务器上提供一段XML文本,然后我们在程序里去访问这个服务器,再对得到的XML文本进行解析.
进入到D:\application_software\Apache\htdocs目录下,新建一个get_data,xml文件,编辑文件如下:
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chorme</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Goole Play</name>
<version>2.3</version>
</app>
</apps>
以上是XML格式的内容
然后在浏览器中访问 http://127.0.0.1/get_data.xml这个网址,显示下述内容:
127.0.0.1是回送地址,指本地机,一般用来测试使用.
回送地址(127.x.x.x)是本机回送地址(Loopback Address)即主机IP堆栈内部的IP地址,
主要用于网络软件测试以及本地机进程间通信,
无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何网络传输。
Pull解析方式(用法简单,语义复杂)
private void sendRequestWithOkHttp() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//首先创建一个OkHttpClient实例,Client是代理人的意思
OkHttpClient client=new OkHttpClient();
//创建一个request对象来发起一条HTTP请求
Request request=new Request.Builder()
//指定访问的服务器是电脑本机
//将Http请求的地址改成了http://211.67.16.16/get_data.xml,这个地址对于手机来说就是电脑本机的IP,
//当然你也可以写成是http://http://127.0.0.1/get_data.xml这两个地址是一样的都是指的是电脑的IP地址
.url("http://211.67.16.16/get_data.xml").build();
//创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据
//Response response=client.newCall(request).execute();
Response response=client.newCall(request).execute();
String responseData=response.body().string();
//--------------------------------
//调用parseXMLWithPull()方法来解析服务器返回的数据
parseXMLWithPull(responseData);
//showResponse(responseData);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private void parseXMLWithPull(String xmlData) {
try {
//首先获取到一个XmlPullParserFactory实例
XmlPullParserFactory factory=XmlPullParserFactory.newInstance();
//借助XmlPullParserFactory实例来获取xmlPullParser对象
XmlPullParser xmlPullParser=factory.newPullParser();
//调用xmlPullParser的setInput()方法将服务器返回的XML数据设置进去开始解析
xmlPullParser.setInput(new StringReader(xmlData));
//-------------解析过程----------------
//调用getEventType()方法获取当前解析事件
int eventType=xmlPullParser.getEventType();
String id="";
String name="";
String version="";
//while循环中不断的进行解析
//如果当前的解析事件不等于XmlPullParser.END_DOCUMENT,说明解析工作还没完成
//调用next()方法获取下一个解析事件
while (eventType!=XmlPullParser.END_DOCUMENT){
String nodeName=xmlPullParser.getName();
switch (eventType){
//开始解析某个结点
case XmlPullParser.START_TAG:{
//若节点名等于id,name,或version
//调用nextText()方法获取节点的具体内容
if ("id".equals(nodeName)){
id=xmlPullParser.nextText();
}else if ("name".equals(nodeName)){
name=xmlPullParser.nextText();
}else if ("version".equals(nodeName)){
version=xmlPullParser.nextText();
}
break;
}
//完成解析某个结点
case XmlPullParser.END_TAG:{
if ("app".equals(nodeName)){
Log.d("MainActivity","id is "+id);
Log.d("MainActivity","name is "+name);
Log.d("MainActivity","version is "+version);
}
break;
}
default:
break;
}
//调用next()方法获取下一个解析事件
eventType=xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
我自己电脑的IP地址
自己电脑的IP地址
测试Web时候的本机地址127.0.0.1是不是为了防止暴露自己的本地IP地址,防黑客???
http://127.0.0.1/get_data.xml
总结:主要是在得到服务器返回的数据后,不再直接将其展示,而是调用parseXMLWithPull()方法来解析服务器返回的数据
- 1.首先获取到一个XmlPullParserFactory实例
- 2.借助XmlPullParserFactory实例来获取xmlPullParser对象
- 3.调用xmlPullParser的setInput()方法将服务器返回的XML数据设置进去开始解析
解析过程如下:
1.调用getEventType()方法获取当前解析事件
2.while循环中不断的进行解析
3.如果当前的解析事件不等于XmlPullParser.END_DOCUMENT,说明解析工作还没完成,调用next()方法获取下一个解析事件
SAX解析方式(用法复杂,语义清楚)
- 1.新建一个类继承DefaultHandler,并重写父类的5个方法
//新建一个类ContentHandler继承DefaultHandler并重写父类的5个方法
//其中startElement characters endElement这三个方法是有参数的
//从XML中解析出的数据就会以参数的形式传入到这些方法中
public class ContentHandler extends DefaultHandler {
private String nodename;
//StringBuilder的主要操作是append和insert方法
//每个方法都能将给定的数据转换成字符串
private StringBuilder id;
private StringBuilder name;
private StringBuilder version;
//在开始进行XML解析的时候调用
@Override
public void startDocument() throws SAXException {
id=new StringBuilder();
name=new StringBuilder();
version=new StringBuilder();
}
//开始解析某个节点的时候调用
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//localName参数记录当前结点名
nodename=localName;
}
//获取节点中内容的时候调用
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
//根据当前结点名判断将内容添加到哪一个StringBuilder对象中
if ("id".equals(nodename)){
id.append(ch,start,length);
}else if ("name".equals(nodename)){
name.append(ch,start,length);
}else if ("version".equals(nodename)){
version.append(ch,start,length);
}
}
//完成解析某个节点的时候调用
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
//如果app结点解析完成,就打印id,name和version的内容
if ("app".equals(localName)){
//id,name和version中都可能是包括回车或者换行符的,打印之前需要
//调用trim()方法去掉回车和换行
Log.d("ContentHandler","id is "+id.toString().trim());
Log.d("ContentHandler","name is "+name.toString().trim());
Log.d("ContentHandler","version is "+version.toString().trim());
//并且打印完最后将StringBuilder清空掉,不然会影响下一次内容的读取
id.setLength(0);
name.setLength(0);
version.setLength(0);
}
}
//在完成整个XML解析的时候调用
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
}
private void parseXMLWithSAX(String xmlData) {
try {
//先创建一个SAXParserFactory对象
SAXParserFactory factory=SAXParserFactory.newInstance();
//然后再获取到XMLReader对象
XMLReader xmlReader=factory.newSAXParser().getXMLReader();
ContentHandler handler=new ContentHandler();
//将ContentHandler的实例设置到XMLReader中
xmlReader.setContentHandler(handler);
//调用parse()开始执行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
} catch (SAXException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
总结:调用parseXMLWithSAX()方法来解析服务器返回的数据.
- 1.首先创建一个SAXParserFactory对象
- 2.然后再获取到XMLReader对象
1.在这之前我们需要先创建一个ContentHandler类继承DefaultHandler,并重写父类的5个方法
2.其中startElement characters endElement这三个方法是有参数的,从XML中解析出的数据就会以参数的形式传入到这些方法中
3.开始进行XML解析的时候调用startDocument()
4.开始解析某个节点的时候调用startElement(String uri, String localName, String qName, Attributes attributes)
5.获取节点中内容的时候调用characters(char[] ch, int start, int length)
6.完成解析某个节点的时候调用endElement(String uri, String localName, String qName)
7.完成整个XML解析的时候调用endDocument() - 3.将ContentHandler的实例设置到XMLReader中
- 4.调用parse()开始执行解析
解析JSON格式数据
进入到D:\application_software\Apache\htdocs目录下新建一个get_data.json的文件,编辑这个文件,加入json格式内容
[{"id":"5","version":"5.5","name":"Clash of Clans"},
{"id":"6","version":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}]
在浏览器中访问http://127.0.0.1/get_data.json,出现如下图所示内容
JSONObject解析方式
使用JSONObject解析JSON格式数据很简单
private void parseJSONWithJSONObject(String jsonData) {
try {
//由于在服务器中定义的是一个JSON数组
// 所以首先将服务器中返回的数据传入到了一个JSONArray对象中
JSONArray jsonArray=new JSONArray(jsonData);
//循环遍历这个JSONArray
for (int i=0;i<jsonArray.length();i++){
//从中取出的每一个元素都是一个JSONObject对象
//每个JSONObject对象中又包含id,name和version这些数据
JSONObject jsonObject=jsonArray.getJSONObject(i);
//调用getString()方法将这些数据取出
String id=jsonObject.getString("id");
String name=jsonObject.getString("name");
String version=jsonObject.getString("version");
Log.d("MainActivity","id is "+id);
Log.d("MainActivity","name is "+name);
Log.d("MainActivity","version is "+version);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
总结:用JSONObject解析JSON型数据
- 1.由于在服务器中定义的是一个JSON数组
- 2.所以首先将服务器中返回的数据传入到了一个JSONArray对象中
- 3.循环遍历这个JSONArray
- 4.从中取出的每一个元素都是一个JSONObject对象
- 5.每个JSONObject对象中又包含id,name和version这些数据
- 6.调用getString()方法将这些数据取出
GSON解析方式
GSON这是谷歌给我们提供的开源库,比JSONObject更简单!!
首先需要添加GSON库的依赖
compile 'com.google.code.gson:gson:2.7'
GSON可以将一段JSON格式的字符串自动映射成一个对象,从而不需要我们再手动编写代码进行解析了
比如一段JSON格式的数据如下所示:
{"name":"Tom","age":20}
我们只需定义一个Person类,并加入name和age两个字段,然后简单的调用如下代码就可以将JSON格式的数据自动解析成一个Person对象了.
Gson gson=new Gson();
Person person=gson.fromJson(jsonData,Person.class);
如果要解析的是一个JSON数组会麻烦一点,需要借助TypeToken将期望解析成的数据类型传入到fromJson()方法中.
如下所示:
Gson gson=new Gson();
List<App> appList=gson.fromJson(jsonData,new TypeToken<List<App>>(){}.getType());
上面代码中期望解析成的数据类型是 App,引用对象也是一种数据类型(java知识)
public class App {
//定义一个App类
//加入id,name,version三个字段
//为什么加入这三个字段呢
//因为返回的JSON型数据中包含这三个字段
private String id;
private String name;
private String version;
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setVersion(String version) {
this.version = version;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getVersion() {
return version;
}
}
private void parseJSONWithGSON(String jsonData) {
Gson gson=new Gson();
List<App> appList=gson.fromJson(jsonData,new TypeToken<List<App>>(){}.getType());
for (App app:appList){
//现在我们便可以采用面向对象的方法来获取我们想要获取的解析完的JSON型数据
//调用app.getId(),app.getName(),app.getVersion()即可
Log.d("MainActivity","id is "+app.getId());
Log.d("MainActivity","name is "+app.getName());
Log.d("MainActivity","version is "+app.getVersion());
}
}
总结:使用GSON的最大的好处是能用面向对象的思维来解决问题简化了代码的操作
- 1.导入闭包来添加GSON库的依赖
- 2.编写具体的类,添加字段与解析返回的JSON型数据相对应(比如JSON型数据里面有id,name,version这三种不同类型的数据那么我们在编写我们的类的时候我们就要在这个类中引入id,name,version这三个字段),进而将一段JSON格式的字符串自动映射成一个对象,从而不需要手动编写代码进行解析了-----该思维跟LitePal很像
- 3.采用如下代码将JSON数据解析成一个具体的对象
Gson gson=new Gson();
//如果JSON型数据不是数组
Person person=gson.fromJson(jsonData,Person.class);
//如果JSON型数据是数组
//期望解析成的数据类型是App
List<App> appList=gson.fromJson(jsonData,new TypeToken<List<App>>(){}.getType());
- 4.采用面向对象的方法调用解析完的JSON型数据
app.getId()
app.getName()
app.getVersion()
网络编程需要注意的点:
这部分的内容在项目中通常会作为一个工具类:即我们新建一个HttpUtil类,将这些通用的网络操作提取到一个公共的类里,并提供一个静态的方法,当想要发起网络请求的话,只需简单的调用这个方法即可.
同时网络请求多属于耗时的操作,而sendHttpRequest()方法内部并没有开启子线程,这样就有可能导致在调用sendHTTPRequest()方法的时候使得主线程被阻塞住.
如果我们在sendHTTPRequest()方法中开启了一个线程来发起HTTP请求,那么服务器响应的数据是无法进行返回的,所有耗时的操作都是在子线程里进行的,sendHTTPRequest()方法会在服务器还没来得及响应的时候就执行结束了,当然也就无法返回响应的数据了.
我们解决这个问题的办法是采用java的回调机制.有两种方法一种方法是使用HttpURLConnection写法,一种是采用OkHttp的写法,前者需要我们手写回调接口;后者已经给我们封装好了我们只需要调用就好了.
- 1.HttpURLConnection写法
首先我们先定义一个接口,比如将它命名为HttpCallbackListener
public interface HttpCallbackListener {
//当服务器成功响应我们请求的时候调用
//onFinish()方法中带的参数代表服务器返回的数据
void onFinish(String response);
//当进行网络操作出现错误的时候调用
//onError()方法中带的参数记录着错误的详细信息
void onError(Exception e);
}
public class HttpUtil {
public static void sendHttpRequest(final String address,final HttpCallbackListener listener){
//注意子线程是无法通过return语句来返回数据的,
// 所以我们将服务器响应的数据传入HttpCallbackListener的onFinish()方法中
// 如果出现了异常就将异常原因传入到onError()方法中
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection=null;
try {
URL url=new URL(address);
connection=(HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in=connection.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
StringBuilder response=new StringBuilder();
String line;
while ((line=reader.readLine())!=null){
response.append(line);
}
if (listener!=null){
//回调onFinish()方法
listener.onFinish(response.toString());
}
} catch (Exception e) {
if (listener!=null){
//回调onError()方法
listener.onError(e);
}
}finally {
if (connection!=null){
connection.disconnect();
}
}
}
}).start();
}
}
HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
@Override
public void onFinish(String response) {
//当服务器成功相应的时候
//在这里根据返回的内容执行具体的逻辑操作
}
@Override
public void onError(Exception e) {
//如果出现了异常
//在这里对异常情况进行处理
}
//这样我们便巧妙的利用回调机制将响应数据成功返回给调用方了
});
- 2.使用OkHttp写法
//okhttp3.Callback这个参数,这是Okhttp库中自带的一个回调接口
public static void sendOkHttpRequest(String address,okhttp3.Callback callback){
OkHttpClient client=new OkHttpClient();
Request request=new Request.Builder()
.url(address)
.build();
//在这个地方并没有用我们之前的execute()方法,而是调用了一个enqueue()方法并将okhttp3.Callback作为参数传入
//OkHttp在enqueue()方法的内部已经帮我们开好子线程了,然后会在子线程中去执行HTTP请求
//将最终的请求结果回调到okhttp3.Callback当中
client.newCall(request).enqueue(callback);
}
我们调用sendOkHttpRequest()方法时可以这么写
HttpUtil.sendOkHttpRequest("http://www.baidu.com", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//在这里对异常进行处理
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//得到服务器返回的具体内容
String response=response.body().string();
}
});
不管是HttpURLConnection还是OkHttp,最终回调接口都还是在子线程中运行的,我们不可以在这里进行UI操作,除非借助renOnUiThread()方法来进行线程转换
服务Service
服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行.
服务并不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程,当某个应用程序进程被杀掉时,所依赖于该进程的服务也会停止运行.
服务不会自动开启线程,所有的代码都是默认运行在主线程中的,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则有可能会出现主线程被堵塞住的情况
Android的多线程编程
当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等原因,服务器不一定会立刻响应我们的请求,如果不将这类操作放到子线程里面去执行,就会导致主线程被堵塞住,从而影响用户对软件的正常使用
我们一般采取使用匿名类的方法来开启一个新线程
new Thread(new Runnable() {
@Override
public void run() {
//处理具体的逻辑
}
}).start();
}
});
子线程中更新UI
Android的UI线程也是线程不安全的,如果想要更新应用程序中的UI元素,必须在主线程中进行,否则会出现异常(也就是若在子线程中更新UI元素会导致程序崩溃).但是有的时候我们需要在子线程中去执行一些耗时的任务,然后根据任务的执行结果来更新相应的UI控件.Android为我们提供了一套异步消息处理的使用方法-----------借助于Handler,完美解决了在子线程中进行UI操作的问题
借助Handler的异步处理方法来更新主线程UI
之前我们已经讲了Hander的用法了,我们今天不进行过多的讨论,只是讲解大体的思路:
首先在主线程创建一个Handler对象,重写其handleMessage(Message msg)方法,然后加一个switch判断 根据msg.what的值来具体执行具体的逻辑,然后新建一个新的线程在新的线程里面声明Message message.what 然后借助于handler.sendMessage(message)方法来发送消息 ,发送的这条消息会被主线程中的Handler的handleMessage()方法接收,然后根据msg.what的不同来执行不同的逻辑.
public class MainActivity extends AppCompatActivity {
private Button mBtnChange_text;
private TextView mTvText;
public static final int UPDATE_TEXT=1;
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATE_TEXT:
mTvText.setText("nice to meet you");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnChange_text=findViewById(R.id.change_text);
mTvText=findViewById(R.id.textshow);
mBtnChange_text.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Message message=new Message();
message.what=UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
}
});
}
}
Android异步消息处理主要由4部分组成:Message,Handler,MessageQueue,Looper
Message:是线程之间传递消息,它可以内部携带少量的信息,用于在不同的线程之间交换数据.除了Message的what字段还有arg1和arg2字段来携带一些整型数据,使用obj字段携带一个object对象
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg1;
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg2;
/**
* An arbitrary object to send to the recipient. When using
* {@link Messenger} to send the message across processes this can only
* be non-null if it contains a Parcelable of a framework class (not one
* implemented by the application). For other data transfer use
* {@link #setData}.
*
* <p>Note that Parcelable objects here are not supported prior to
* the {@link android.os.Build.VERSION_CODES#FROYO} release.
*/
public Object obj;
/**
* Optional Messenger where replies to this message can be sent. The
* semantics of exactly how this is used are up to the sender and
* receiver.
*/
Handler:顾名思义也就是处理者的意思,主要用于发送和接收消息的,发送消息用的是handler.sendMessage()方法,发出的消息经过辗转处理后会传递到Handler的handMessage()方法中
MessageQueue:消息队列的意思,主要用于存放所有通过Handler发送的消息,每个线程只有一个MessageQueue对象
Looper:Looper是每个MessageQueue的管家,调用Looper的loop()方法后,会进入到一个无线的循环中去,然后每发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中.每个线程也只有一个Looper对象
几点说明:子线程中如果想要创建Handler对象那么必须首先要先创建一个Looper即用Looper.prepare()创建一个Looper实例,同时它会自动配套MessageQueue,然后便可以创建Hander对象(可以通过查看源码得知)最后启动Looper.loop()方法
在主线程中系统已经初始化好了一个Looper对象,我们直接创建Hander对象即可,然后可以通过Handler来发送处理消息了.
若Handler在主线程中创建的则此时handleMessage()也会在主线程中进行,于是我们便可在此进行UI操作了.
我们如何区分哪里是主线程哪里是子线程呢,系统默认所创建的东西比如A都是默认在主线程创建的,除非你特别的声明,你先声明了一个子线程然后在子线程中的run()方法中又声明了B.那么B便是在子线程中创建的.
使用AsyncTask
使用AsyncTask即使你对异步消息处理的机制完全不了解,也可以十分简单的从子线程切换到主线程,AsyncTask背后的实现原理也是基于异步消息处理机制的,只是Android已经给我们做好了封装而已.
AsyncTask是一个抽象类,如果我们想要使用它,我们必须创建一个子类去继承它,在继承的时候为AsyncTask指定3个泛型参数
public class DownloadTask extends AsyncTask<Void ,Integer,Boolean> {
}
Params:在执行AsyncTask时需要传入的参数,可用于后台任务中使用
Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
Result:当任务执行完毕后,如果需要对结果进行返回,使用这里指定的泛型作为返回值的类型
public class DownloadTask extends AsyncTask<Void ,Integer,Boolean> {
//在后台任务开始执行之前调用,用于进行一些界面上的初始化操作
@Override
protected void onPreExecute() {
ProgressDialog.show(MainActivity.this,"ni xiang gan ma ");
}
//这个方法的所有代码都会在子线程中运行,在这里处理所有的耗时任务
//任务一旦完成就可以通过return语句来执行结果返回
//因为该代码是在子线程中运行的所以这个方法中是不可以进行UI操作的
@Override
protected Boolean doInBackground(Void... voids) {
try{
while (true){
//这是一个虚构的方法
int downloadPercent=doDownload();
//如果要更新UI元素,可以调用publishProgress(Progress...)方法来完成
publishProgress(downloadPercent);
if (downloadPercent>=100){
break;
}
}
}catch (Exception e){
return false;
}
return true;
}
//该方法中携带的参数就是后台任务中传递过来的
//这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新
@Override
protected void onProgressUpdate(Integer... values) {
//在这里更新下载进度
progressDialog.setMessage("Download"+values[0]+"%");
}
//后台任务执行完毕并通过return语句进行返回时,会很快调用这个方法
//返回的数据会作为参数传递到这个方法中,可以利用返回的数据来进行一些UI操作
//比如提醒任务执行的结果,关闭掉进度对话框等等
@Override
protected void onPostExecute(Boolean aBoolean) {
//关闭进度对话框
progressDialog.dismiss();
//在这里提示下载结果
if (aBoolean){
Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
}
}
}
如果想要启动这个任务,只需要编写如下代码:
new DownloadTask().execute();
使用AsyncTask的诀窍是:在doInBackground()方法中执行具体的耗时任务;在onProgressUpdate()方法中进行UI操作;在onPostExecute()方法中执行一些任务的收尾工作.
AsyncTask
Service
服务的生命周期
服务的生命周期服务的生命周期
创建服务
创建服务和创建活动创建广播创建内容提供器都很像
创建一个类继承Service-----服务
创建一个类继承Activity------活动
创建一类继承BroadcastReceiver-----广播
创建一个类继承ContentProvider---内容提供器
并且四大组件都需要在AndroidManifest,xml文件中注册
绑定本地Service并与之通信
如果访问者和Service之间需要进行方法调用或者交换数据,则应该使用bindService()和unbindService()方法启动,关闭service.
一.首先我们先来看一下这两个方法(来源于API):bindService方法相对unbindService()来说比较复杂
@Override
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
return mBase.bindService(service, conn, flags);
}
-
1.service:该参数通过Intent指定要启动的Service
-
2.conn:该参数是一个ServiceConnection对象,该对象用于监听访问者和Service之间的连接情况.当访问者与Service之间连接成功时将回调该ServiceConnection对象的onServiceConnected(ComponentName name, IBinderservice)方法;当Service所在的宿主进程由于异常中止或者其他原因中止,导致该Service与访问者之间断开连接时便回调该ServiceConnection对象的onServiceDisconnected(ComponentName name)方法.
注意:当调用者主动通过unBindService()方法断开与Service的连接时,ServiceConnection对象的onServiceDisconnected(ComponentName name)方法并不会被调用.
- 3.flags:指定绑定时是否自动创建Service(如果Service还未创建).该参数可指定为0(不自动创建)或BIND_AUTO_CREATE(自动创建)
总结:如果我们想调用这个bindService(),
1.首先我们要获取到我们想要启动的Service(通常这个Service我们自己定制,并且该Service通常会实现自己的功能);
2.其次,我们要获取到ServiceConnection的对象;
3.最后指定是否自动创建Service(这个相比较前两者不是很重要)
- 1.我们先来创建自己的Service
代码如下:
public class MyService extends Service {
//我们创建Binder的子类,Binder实现了
//通过继承Binder类实现了一个IBinder对象,
//这个DownloadBinder类是Service类中的内部类,
//这对于绑定本地Service并与之进行通信的场景是一种常见的情形
class DownloadBinder extends Binder {
//DownloadBinder类中可以写MyService中提供哪些功能的逻辑
//这里定义了两个方法startDownload()和getProgress()方法
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
private DownloadBinder mBinder = new DownloadBinder();
public MyService() {
}
//这个方法是Service中唯一的一个抽象方法,必须要在子类中实现
//IBinder对象相当于Service组件的内部钩子,该钩子关联到绑定的Service组件,
//当其他应用程序组件绑定该Service时,Service将会把IBinder对象返回给其他程序组件,
//其他程序组件通过该IBinder对象即可与Service组件进行实时通信
//绑定本地Service的情况下,onBind(Intent intent)方法所返回的IBinder对象
//将会传给ServiceConnection对象里的onServiceConnected(ComponentName name, IBinder service)方法中的service 参数
//这样访问者便可以通过该IBinder对象与Service进行通信
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
//Service的onBind()方法所返回的IBinder对象来说,
//它可以被当做是该Service组件所返回的代理对象,
//Service允许客户端通过该IBinder对象来访问Service内部的数据,
//这样即可实现客户端与Service之间的通信.
return mBinder;
}
//该方法在服务创建的时候调用
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
Intent intent=new Intent(this,MainActivity.class);
PendingIntent pi =PendingIntent.getActivity(this,0,intent,0);
Notification notification= new NotificationCompat.Builder(this).setContentTitle("This is content title")
.setContentText("This is content text").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)).build();
startForeground(1,notification);
}
//onStartCommand()方法会在每次服务启动的时候调用
//如果我们希望服务一旦启动就去执行某个动作,可以将逻辑写在onStartCommand()方法中
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
//onDestroy()方法会在服务销毁的时候调用
//当服务销毁时,我们应该在onDestroy()方法中回收那些不再使用的资源
@Override
public void onDestroy() {
Log.d("MyService", "onDestroy executed");
super.onDestroy();
}
}
我们不难看出这里面最最重要的当属下面的方法
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
IBinder对象相当于Service组件的内部钩子,该钩子关联到绑定的Service组件,当其他应用程序组件绑定该Service时,Service将会把IBinder对象返回给其他程序组件,其他程序组件通过该IBinder对象即可与Service组件进行实时通信.
因为该方法是抽象方法所以必须要在子类中实现所以我们定义了它的子类DownloadBinder,并在它的子类中定义了它可以实现的功能
class DownloadBinder extends Binder {
//DownloadBinder类中可以写MyService中提供哪些功能的逻辑
//这里定义了两个方法startDownload()和getProgress()方法
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
- 2.然后我们在Activity中获取到ServiceConnection的对象
//声明我们在MyService中定义的内部类DownloadBinder
//该内部类实现了service和activity的绑定
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//通过IBinder对象service向下转型为DownloadBinder实例
//有了这个实例,活动和服务的关系便更紧密了
//我们可以在活动中根据具体的场景来调用DownloadBinder中任何public方法
//实现了指挥服务干什么服务就去干什么的功能
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.getProgress();
downloadBinder.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
Activity中完整代码如下
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button mBtnStartService;
private Button mBtnStopService;
private Button mBtnBindService;
private Button mBtnUnbindService;
private Button mBtnIntentService;
//声明我们在MyService中定义的内部类DownloadBinder
//该内部类实现了service和activity的绑定
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//通过IBinder对象service向下转型为DownloadBinder实例
//有了这个实例,活动和服务的关系便更紧密了
//我们可以在活动中根据具体的场景来调用DownloadBinder中任何public方法
//实现了指挥服务干什么服务就去干什么的功能
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.getProgress();
downloadBinder.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnStartService = findViewById(R.id.start_service);
mBtnStopService = findViewById(R.id.stop_service);
mBtnBindService = findViewById(R.id.bind_service);
mBtnUnbindService = findViewById(R.id.unbind_service);
mBtnIntentService=findViewById(R.id.start_intent_service);
mBtnStartService.setOnClickListener(this);
mBtnStopService.setOnClickListener(this);
mBtnBindService.setOnClickListener(this);
mBtnUnbindService.setOnClickListener(this);
mBtnIntentService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = null;
switch (v.getId()) {
case R.id.start_service:
//启动和停止服务的方法都是借助Intent来实现的
intent = new Intent(MainActivity.this, MyService.class);
//启动服务的方法和启动活动很像!!!
startService(intent);
break;
case R.id.stop_service:
intent = new Intent(MainActivity.this, MyService.class);
//停止服务
stopService(intent);
break;
case R.id.bind_service:
Intent bindIntent = new Intent(MainActivity.this, MyService.class);
//绑定服务
bindService(bindIntent, connection, BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
Intent unbindIntent = new Intent(MainActivity.this, MyService.class);
//解绑服务
unbindService(connection);
break;
case R.id.start_intent_service:
//打印主线程的id
Log.d("MainAcitiviy","Thread id is "+Thread.currentThread().getId());
Intent intentService = new Intent(MainActivity.this, MyIntentService.class);
startService(intentService);
break;
default:
break;
}
}
}
unbindService()方法
@Override
public void unbindService(ServiceConnection conn) {
mBase.unbindService(conn);
}
该方法只需要接收一个参数 ServiceConnection
总结: unbindService()方法
解绑服务是用到unbindService()方法,unbindService()方法接收一个参数:ServiceConnection对象
虽然说这个方法的参数只有一个ServiceConnection
我们知道创建一个ServiceConnection对象需要,指定一个IBinder对象所以我们要创建的步骤和bindService()差不多
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//通过IBinder对象service向下转型为DownloadBinder实例
//有了这个实例,活动和服务的关系便更紧密了
//我们可以在活动中根据具体的场景来调用DownloadBinder中任何public方法
//实现了指挥服务干什么服务就去干什么的功能
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.getProgress();
downloadBinder.startDownload();
}
1.首先我们要获取到ServiceConnection的对象;
其次我们要获取到我们想要启动的Service(通常这个Service我们自己定制,并且该Service通常会实现自己的功能);
2.其次我们根据要获取一个ServiceConnection对象需要获取一个IBinder对象转而去定义一个我们的Service
3.最后调用unbindService()方法,将参数传进去即可
使用前台服务
前台服务服务系统的优先级还是比较低的,当系统出现内存不足的情况时,就有可能回收掉正在后台运行的服务.如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务.前台服务的最大区别在于,它会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果.
//该方法在服务创建的时候调用
//并且这里准确的说是创建了一个前台服务
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
//使用前台服务,跟调用通知的方法很像,只不过最后不是调用NotificationManager.notify()将通知显示出来
//而是调用startForeground()方法来让MyService变成一个前台服务,并在系统的状态栏中显示出来
Intent intent=new Intent(this,MainActivity.class);
PendingIntent pi =PendingIntent.getActivity(this,0,intent,0);
Notification notification= new NotificationCompat.Builder(this).setContentTitle("This is content title")
.setContentText("This is content text").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)).build();
//调用startForeground()方法来让MyService变成一个前台服务,并在系统的状态栏中显示出来
startForeground(1,notification);
}
IntentService
IntentService服务中的代码都是默认运行在主线程中的,如果直接在主线程中处理一些逻辑,就很容易出现ANR情况.这个情况下就需要我们使用多线程编程的技术了,我们应该在服务的每个具体的方法里开启一个子线程,然后在这里处理那些耗时的逻辑.
public class MyServcie extends Service{
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable(){
@Override
public void run(){
//处理具体的逻辑
}
}).start();
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
}
这种服务一旦开启便停不下来来,我们必须调用stopService()或者stopSelf()方法才能让服务停下来,如果想要实现一个服务在执行完毕后自动停止的功能.
public class MyServcie extends Service{
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable(){
@Override
public void run(){
//处理具体的逻辑
stopSelf();
}
}).start();
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
}
但是上面那些太麻烦了,为了简单的额创建一个异步的会自动停止的服务,Android专门提供了一个IntentService类,这个类就技能开启新线程执行耗时的逻辑,又能在逻辑操作完成之后自动关闭线程
public class MyIntentService extends IntentService {
public MyIntentService() {
//调用父类的有参构造函数
super("MyIntentService");
}
//要在子类中去实现onHandleIntent()这个抽象方法
//在这个方法中处理一些具体的逻辑,而且不用担心ANR的问题,因为这个方法已经在子线程中运行了
@Override
protected void onHandleIntent(@Nullable Intent intent) {
//打印当前线程的id
//在这个方法中处理一些具体的逻辑,而且不用担心ANR的问题,因为这个方法已经在子线程中运行了
Log.d("MyIntentService","Thread id is "+Thread.currentThread().getId());
}
//因为这个服务是在运行结束后应该是会自动停止的,所以我们要再重写onDestory()方法
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService","onDestory executed");
}
}
IntentService和普通的Service的用法没什么两样
Intent intentService = new Intent(MainActivity.this, MyIntentService.class);
startService(intentService);
服务的完整一个代码实例
public interface DownloadListener {
void onProgress(int progress);
void onSuccess();
void onFailed();
void onPaused();
void onCanceled();
}
public class DownloadService extends Service {
private DownloadTask downloadTask;
private String downloadUrl;
public DownloadService() {
}
//创建了一个DownloadListener的匿名类实例,并在匿名类实例中重写了
// onProgress onSuccess onFailed onPaused onCancled 这五个方法
private DownloadListener listener=new DownloadListener() {
@Override
public void onProgress(int progress) {
//调用getNotificationManager()方法构建了一个用于显示下载进度的通知
//调用NotificationManager.notify()方法去触发这个通知
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,"Download Pause",Toast.LENGTH_SHORT).show();
}
@Override
public void onCanceled() {
downloadTask=null;
stopForeground(true);
Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_SHORT).show();
}
};
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
private DownloadBinder mBinder=new DownloadBinder();
//为了能让DownloadService可以和活动进行通信,我们又创建了DownloadBinder,
// 并提供了pauseDownload startDownload cancelDownload这三个方法
class DownloadBinder extends Binder{
public void startDownload(String url){
if (downloadTask==null){
downloadUrl=url;
//我们创建了一个DownloadTask实例,并把刚才的DownloadListener作为参数传入
downloadTask=new DownloadTask(listener);
//调用execute()方法开启下载,并将下载文件的URL地址传入到execute()方法中
downloadTask.execute(downloadUrl);
//为了让这个下载服务称为一个前台服务,调用了startForeground()方法
//这样便会在系统状态栏中创建一个持续运行的通知了
startForeground(1,getNotification("Downloading...",0));
Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
}
}
public void pauseDownload(){
if (downloadTask!=null){
//简单调用了一下DownloadTask.pauseDownload()方法
downloadTask.pauseDownload();
}
}
public void cancelDownload(){
if (downloadTask!=null){
downloadTask.cancelDownload();
}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();
}
}
}
private 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+"%");
//setProgress()方法接收三个参数第一个是传入通知的最大进度条,第二个参数传入通知的当前进度
//第三个参数传入模糊进度条.设置完毕后通知上便会有进度条显示出来了
builder.setProgress(100,progress,false);
}
return builder.build();
}
}
public 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;
}
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Integer doInBackground(String... strings) {
InputStream is =null;
RandomAccessFile savedFile=null;
File file =null;
try {
//记录已下载的文件长度
long downloadedLength=0;
//从参数中获取到了下载的URL地址
String downloadUrl=strings[0];
//根据URL地址解析出了下载的文件名
String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
//将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录
String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
file =new File(directory+fileName);
//判断Download目录中是否已经存在要下载的文件了
if (file.exists()){
//若存在则读取已下载的字节数,这样可以在后面启动断电续传的功能
downloadedLength=file.length();
}
//调用getContentLength()方法来获取待下载文件的总长度
long contentLength=getContentLength(downloadUrl);
//如果文件的长度等于0说明文件有问题
if (contentLength==0){
return TYPE_FAILED;
//如果文件长度等于已下载文件长度,说明文件已经下载完了
}else if (contentLength==downloadedLength){
return TYPE_SUCCESS;
}
//使用OkHttp来发送一条网络请求
OkHttpClient client=new OkHttpClient();
Request request=new Request.Builder().
//请求中添加了一个header,用于告诉服务器我们想要从哪个字节开始下载
//因为已经下载过的部分就不要再重复下载了
addHeader("RANGE","bytes="+downloadedLength+"-").
url(downloadUrl).build();
//读取服务器相应的数据
Response response=client.newCall(request).execute();
if (response!=null){
//使用java文件流的方式,不断从网上读取数据,不断写入本地,直到文件全部下载完成为止
is=response.body().byteStream();
savedFile=new RandomAccessFile(file ,"rw");
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=total+len;
savedFile.write(b,0,len);
//计算已下载的百分比
int progress= (int) ((total+downloadedLength)*100/contentLength);
//如果用户触发暂停或者取消操作,我们调用publishProgress()方法进行通知
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 (IOException e) {
e.printStackTrace();
}
}
return TYPE_FAILED;
}
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.body().close();
return contentLength;
}
return 0;
}
@Override
protected void onProgressUpdate(Integer... values) {
//首先从参数中获取到当前的下载进度
int progress=values[0];
//和上一次的下载进度进行对比
if (progress>lastProgress){
//如果有变化则调用DownloadListener.onProgress()方法通知下载进度更新
listener.onProgress(progress);
lastProgress=progress;
}
}
@Override
protected void onPostExecute(Integer integer) {
//根据参数中传入的下载状态来进行回调
switch (integer){
//下载成功调用DownloadListener.onSuccess()方法
case TYPE_SUCCESS:
listener.onSuccess();
break;
//下载失败调用DownloadListener.onFailed()方法
case TYPE_FAILED:
listener.onFailed();
break;
//暂停下载调用DownloadListener.onPaused()方法
case TYPE_PAUSED:
listener.onPaused();
break;
//取消下载调用DownloadListener.onCancled()方法
case TYPE_CANCELED:
listener.onCanceled();
break;
default:
break;
}
}
public void pauseDownload(){
isPaused=true;
}
public void cancelDownload(){
isCanceled=true;
}
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button mBtnstartdownload;
private Button mBtnpausedownload;
private Button mBtncanceldownload;
private DownloadService.DownloadBinder downloadBinder;
//首先创建了一个ServiceConnection的匿名类
private ServiceConnection connection=new ServiceConnection() {
@Override
//获得了DownloadBinder的实例,有了这个实例,我们便可以在活动中调用服务提供的各种方法了
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder= (DownloadService.DownloadBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnstartdownload=findViewById(R.id.start_download);
mBtnpausedownload=findViewById(R.id.pause_download);
mBtncanceldownload=findViewById(R.id.cancel_download);
mBtnstartdownload.setOnClickListener(this);
mBtnpausedownload.setOnClickListener(this);
mBtncanceldownload.setOnClickListener(this);
Intent intent=new Intent(this,DownloadService.class);
//启动服务
startService(intent);
//绑定服务
bindService(intent,connection,BIND_AUTO_CREATE);
//请求权限
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);
}
}
@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();
case R.id.cancel_download:
downloadBinder.cancelDownload();
break;
default:
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults[0]!=PackageManager.PERMISSION_GRANTED&&grantResults.length>0){
Toast.makeText(this,"拒绝权限将无法使用本程序",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//解除服务
unbindService(connection);
}
}
网友评论