第九章看看精彩的世界——使用网络技术

    若玩手机不能上网,那有什么用,微博、微信、QQ用了大量的网络技术。下面讲到了常用的网络技术:

9.1 WebView的用法

(1)适用范围:应用内展示网页,但却不允许打开系统浏览器,借助WebView可以在自己应用程序中嵌入一个浏览器。目的是应用程序内显示网页。

(2)实现方法:

       1.设置WebView的布局和findViewById获取到WebView的实例

       2.getSettings方法去设置浏览器属性,setJavaScriptEnabled让WebView可以调用JS的脚本

webView_Brower.getSettings().setJavaScriptEnabled(true);

3.最重要的一步,传入WebViewClient实例,功能:一个网页跳转至另一个网页时,仍然在WebView中显示,而非跳出。

webView_Brower.setWebViewClient(new WebViewClient());

4.调用WevView的loadUrl将网址传入。

webView_Brower.loadUrl("http://www.baidu.com");

5.最后加上INTERNET的权限声明。

<uses-permission android:name="android.permission.INTERNET" />

9.2使用Http协议访问网络

       WebView封装的太好,发送Http请求,接受服务响应、解析返回数据为一体,因此不能直观看出Http协议到底如何工作的。手动发送Http请求。

9.2.1 使用HttpURLConnnection

        Android上发送Http请求:HttpURLConnection和HttpClient。Android 6.0之后HttpClient移除,因此只用HttpURLConnection。

      1.建立点击事件以及相应的布局,布局中ScrollView,我们可以滚动其查看屏幕外的内容,ScrollView是一个FrameLayout,若点击,调用sendRequestWithHttpURLConnectoin方法。

     2.开启子线程来发起网络请求,在子线程中利用HttpURLConnection发送一条Http请求。具体来讲是(1)new一个百度网址的URL对象2)调用openConnection方法得到相应实例;(3)设置请求方式,"POST"还是GET;(4)设置连接超时、读取超时ms数。

3.利用connection.getInputStream()获取输入流,利用BufferReader对获取到的输入流进行读取

new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream in = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while((line=reader.readLine())!=null){
response.append(line);
}
}

4.利用ShowResponse方法显示结果,Android不允许在子线程更改UI,必须切换主线程,进行更改元素。

private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在这里显示UI操作,并将结果显示到界面上。
responseText.setText(response);
}
});
}

5.记得使用 reader.close();connection.disconnect();关闭Reader流和Connection连接。若发送的话:

《第一行代码》总结之网络、服务(五)_xml

9.2.2 使用OKHttp

       由Square公司开发,开源库,可以替代HttpURLConnection。接口封装简单,底层实现好。首选网络通信库。

       1.添加依赖关系,compile 'com.squareup.okhttp3:okhttp:3.4.1',自动下载OKHttp和Okio库

       2.创建OkHttpClient实例,为了发起Http请求,创建Request对象,在build之前连缀很多方法来丰富Request对象

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com").build();

3.调用OkHttpClient的newCall方法来创建一个Call对象,并调用execute方法来获取服务器返回的数据

Response response = client.newCall(request).execute(); String respnoseData = response.body().toString();

     4.showResponse一次性显示出来 

     5.若是Post一个请求:

《第一行代码》总结之网络、服务(五)_xml_02

9.3 解析XML格式数据

        网络传输向服务器提交的数据或者从服务器获取的数据一般都有相应的结构格式和语义。常见格式有:XML和JSON。首先需要搭建一个服务器:

       1.Apache服务器下载及搭建。参考链接:

       https://blog.csdn.net/qq_34804120/article/details/78862290(很恶心啊,浪费了大半个早上,才搞好。http://127.0.0.1:8080/get_data.xml。127.0.0.1就是localHost,8080是端口号。根本就没有霖神书上的玩意好么)

       2.写相应的XML文件。Get_data.xml。对该xml进行解析。

<apps>  <app>   <id>1</id>   <name>Google Maps</name>   <version>1.0</version>  </app>  <app>   <id>2</id>   <name>Chrome</name>   <version>2.1</version>  </app>  <app>   <id>3</id>   <name>Google Play</name>   <version>2.3</version>  </app> </apps>

         输入网址,可以显示该xml网页,开始对其进行解析。

9.3.1 Paul解析方式

    1.将请求地址修改为http://10.0.2.2:8080/get_data.xml。

    2.利用自定义parseXMLWITHPull方法对respnoseData进行解析

    3.获取XmlPullParserFactory实例,以此构建XmlPullParser对象,利用setInput方法将数据设置进去开始解析。

    4.getEventType可以得到当前的解析事件。随后在while循环里不停解析。若等于XmlPullParser.END_DOCUMENT,则结束解析,否则执行调用next循环解析。

    5.getName获取当前节点的名字,若等于id,name或version,则利用nextText获取其中内容,每当解析结束一个app节点便将其内容打印出来。

private void parseXMLWITHPull(String respnoseData) {
try {

XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlpullparser = factory.newPullParser();
xmlpullparser.setInput(new StringReader(respnoseData));

int eventType = xmlpullparser.getEventType();
String id = "", name = "", version = "";
while (eventType != XmlPullParser.END_DOCUMENT) {

String nodename = xmlpullparser.getName();
switch (eventType) {
//开始解析某个节点
case XmlPullParser.START_TAG: {
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;
}
eventType = xmlpullparser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

       但不知道为啥,就是解析不出来,调试也好,用别人的代码也好,在我搭建的服务器上就是输出不了啊。

       后面讲了SAX解析格式:现对于Pull更复杂,但语义更清晰。

解析JSON格式数据:Json体积更小、更省流量。但语义较差、不够直观。可以官方提供的JSONObject进行解析,也可以利用谷歌的开源库Gson库来解析JSON格式数据。当然第三方JackSon和FastJSON也不错,最后是一个实践活动说明了OKHttp相较于Http协议通信,强大太多,有相应的回调接口,建议更多使用OKhttp。

                                                            第十章——后台默默的劳动者-探究服务

       服务使得应用程序即使在关闭的情况下依旧可以在后台继续运行。学完任务脱离了初级开发者的身份啦(一定要自信,虽然知道自己还是很有很长的路要走),本章主要聚焦在Android多线程编程(解析异步消息处理机制<Handler、Message的处理机制>、异步通信任务AsynacTask)、服务的基本用法、生命周期以及高阶技巧(前台服务与IntentService),最后是一个下载的实践实例。成长和提高很快,加油加油~~~。我们老师说过一句话:我本科一老师说过:大学阶段没有个20w行代码,你这计算机是白读了,研究生阶段迎头赶上吧。业精于勤荒于嬉,行成于思毁于随。

10.1服务是啥?

       1.适合于执行不需要与用户交互而且还要求长期运行的任务,不依赖于界面,当被切换后台或下一个应用程序时,服务仍然可以运行。其依赖于创建服务时所在的应用程序进程。

10.2 Android多线程编程

       耗时操作譬如说发送网络请求,放在子线程中运行,否则可能会导致主线程被阻塞住。

(一)线程的基本用法

      Android多线程编程与Java类似。

    (1)只需建立一个类继承自Thread,重写父类的run方法,编写耗时逻辑。

class MyThread extends Thread{
@Override
public void run() {
super.run();
}
}

如何启动?保证run方法中的代码可在子线程中运行了;

new MyThread().start();

(2)继承方式耦合性较高,更多我们使用Runnnable接口的方法来定义一个线程。Thread构造函数接受一个Runnable对象,new出的MyThread实现了Runnable的接口,直接将其传至Thread的构造函数里。接着调Start方法。

class MyThread01 implements Runnable{
@Override
public void run() {

}
}
//启动
MyThread01 myThread01 = new MyThread01();
new Thread(myThread01).start();

(3)匿名类实现Runnable接口。

new Thread(new Runnable() {
@Override
public void run() {

}
}).start();

(二)在子线程中更新UI

        Android UI也是线程不安全的,更新UI元素必须在主线程中进行,否则就会出现异常。若在子线程中更新,会出现以下的报错:

《第一行代码》总结之网络、服务(五)_安卓开发_03

         因此Android提供了一套异步消息处理机制,以解决子线程中进行UI操作的问题。

     (1)建立Button和TextView布局的相应的时间响应;

     (2)定义整形常量以表示更新TextView的动作,新增Handler对象并重写父类的HandleMessage方法,在这里对Message具体进行处理。

public Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATE_TEXT:
//在这里进行UI操作。
tv_text.setText("Nice to Meet u!");
break;
default:
break;
}
}
};

        (3)在Button的点击事件为创建Message对象,并将what字段值指定为Update_TEXT,使用sendMessage将字段发送出去,这样handler就会收到该Message,并使用handlerMessage对其进行处理。

(三)解析异步消息处理机制

        Android中的Message、Handler、MessageQueue和Looper。下面对其进行简单介绍:

      (1)Message

      线程之间传递消息,可携带少量信息,用于不同线程交换数据。可以使用what字段,也可以使用arg1和arg2来携带整数数据,使用obj字段来携带Object对象。

      (2)Handler

      处理者,用于发送和处理消息。发送一般是使用sendMessage方法,经过一系列辗转处理后,最终会传递到Handler的handleMessage方法中。

      (3)MessageQueue

       消息队列,存放所有通过Handler发送的消息,这部分会被存放于其中,并等待处理。每个线程只有一个MEssageQueue对象。

      (4)Looper

      MessageQueue的管家,调用Looper的loop方法后,会进入到无限循环中,每当发现MessageQueue有一条消息,将其取出,并交到handlerMessage去处理,每个线程也只有Looper对象。由于Handler是在主线程中创建的,因此handlerMessage也是在主线程中进行,在这里我们可以进行UI操作啦。

《第一行代码》总结之网络、服务(五)_第一行代码_04

(四)使用AsyncTask

        (1)为了更方便我们在子线程中更新操作,Android提供了AsyncTask,借助其更简单从子线程切换至主线程。其内部机制是基于异步消息处理机制的。

       (2)AsyncTask是抽象类,想要使用它,必须用子类去继承它,我们可以为AsyncTask指定3个泛型参数,用途如下:params为执行AsyncTask类传入的参数,可用于后台任务处理;Progress在界面上显示当前的进度;Result对结果进行返回,使用boolean类型。

      (3)AsyncTask中重写的几个方法:

       1.onPreExecute:开始执行之前调用,用于界面初始化操作,譬如显示进度条对话框等;

       2.doInBackground:这里面的代码在子线程中进行,处理所有耗时操作,在这里不可进行UI操作,若需要更新UI元素,反馈当前任务进度,可以使用publishProgress方法来完成。

      3.OnProgressUpdate:当后台调用publishProgress方法,OnProgressUpdate方法的参数为后台传递来的,对UI进行操作和更新。

      4.onPostExecute:当后台执行完毕并使用return返回时,可以利用此时返回的数据进行UI操作,比如提醒任务执行的结果,关闭对话框进度等等。举例(并没有实现每个方法,会在本章最后一节写里面的实现,调用的话new DownLoadTask().execute();来启动任务):

class DownLoadTask extends AsyncTask<Void,Integer,Boolean>{
@Override
protected Boolean doInBackground(Void... params) {
//耗时的下载操作,完成具体的下载任务。
publishProgress();
//将当前下载进度传入并调用onProgressUpdate方法以更新UI操作。
return null;
}

@Override
protected void onPreExecute() {
//显示对话框
super.onPreExecute();
}

@Override
protected void onProgressUpdate(Integer... values) {
//在这里更新进度
super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(Boolean aBoolean) {
//关闭进度个对话框并提示下载结果
super.onPostExecute(aBoolean);
}
}

10.3服务的基本用法

        四大组件之中的Service也是我们学习的重要环节之一。

      (一)定义一个服务

      1.右键APP的文件夹->New->Service->Service,将服务命名为Myservice。

      2.onCreate服务创建时候调用;onStartCommand每次服务启动的时候调用;onDestroy服务销毁的时候调用;onBind是唯一的抽象方法,需要在子类中实现,目前还不涉及,后面再说。(另外:四大组件都必须在xml中注册才能生效,不信你在xml中看)

     (二)启动和停止服务

     主要是借助于Intent来实现。

    (1)建立启动服务和停止服务的两个按钮及相应的点击事件。启动和停止服务的点击事件如下:

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_startService:
//开始服务。
Intent start_intent = new Intent(MainActivity.this,MyService.class);
startService(start_intent);
break;
case R.id.btn_stopService:
//停止服务。
Intent stopIntent = new Intent(MainActivity.this,MyService.class);
stopService(stopIntent);
break;
default:
break;
}
}

(2)在Service中加入打印日志。onCreate是第一次创建时调用,而pmCreateCommand则是每次启动服务时调用,若服务未创建,则两个一起调用,多点击的话后者会调用,结束的时候调用onDestroy.

//服务创建时候调用
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate executed ");
}
//每次服务启动的时候调用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand executed ");
return super.onStartCommand(intent, flags, startId);
}
//服务销毁的时候调用
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy executed ");
}

《第一行代码》总结之网络、服务(五)_网络通信_05

(三)活动与服务之间的通信

       上面的实例:启动服务之后,活动与服务无关。只是活动通知服务一下:你可以启动了,具体做什么不知道,完成的如何也不知道。

       那么如何让活动与服务更紧密一些:那就要用上面的onbind方法了.

       (1).在Myservice类中新建DownloaderBinder类,继承自Binder,模拟实现开始下载和获取进度两个方法。创建DownloaderBinder的实例,在onbind方法中返回这个实例。

private DownloaderBinder mBinder = new DownloaderBinder();
class DownloaderBinder extends Binder{
public void startDownload(){
Log.d(TAG, "startDownload: ");
}
public int getProgress(){
Log.d(TAG, "getProgress: ");
return 0;
}
}

(2)在MainActivity中建立两个绑定和解绑的按钮及相应的点击事件。创建ServiceConnection匿名类,重写onServiceConnected和onServiceDisconnected两个方法。向下转型得到downloadbinder的实例,有了这实例,可以指挥干活了,调用了之前定义的下载和获取进度的方法。

private MyService.DownloaderBinder downloaderBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloaderBinder = (MyService.DownloaderBinder) service;
downloaderBinder.startDownload();
downloaderBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};

(3)绑定活动,通过构建Intent,然后调用bindService来实现。BindService的第一个参数是intent对象,第二个是前面创建的connection实例,第三个是标志位,表示活动与服务绑定之后自动创建服务。

case R.id.btn_bindService:
//开始服务。
Intent bind_Service = new Intent(MainActivity.this,MyService.class);
bindService(bind_Service,connection,BIND_AUTO_CREATE);
break;
case R.id.btn_unbindService:
//停止服务。
Intent unbind_Service = new Intent(MainActivity.this,MyService.class);
unbindService(connection);
break;

《第一行代码》总结之网络、服务(五)_安卓开发_06

(4)MyService不仅可以与MainActivity板顶,也可以和任意一个活动绑定哦,绑定可以获取相应的下载实例。

10.4服务的生命周期

        有onCreate、onStartCommand、onBind和onDestroy等方法都是在服务生命周期内可能调用的方法。

      (1)任何位置调用startService方法,服务会启动起来,并回调onStartCommand方法;若服务未曾创建,则onCreate会先于onStartCommand方法;服务启动后会运行,直到onStopService或StopSelf方法被调用;无论调多少次onStartService,只需要一次onStopService就会结束;

      (2)还可以调用bindService来获得服务持久连接,此时回调bind方法,若未创建过,则会先onCreate再onbind,onBind方法会返回IBinder对象实例。自由通信了已经可以。

      (3)bindService绑定后用unbindService解绑,此时会调用onDestroy方法;startService启动后会调用stopService来结束服务。若同时调用了两个启动绑定方法,则必须同时调用stopService和unbindservice才可会执行onDestroy方法,因为android机制规定了服务被启动和绑定之后,会一直处于运行状态。

10.5 服务的更多技巧

       (一)使用前台服务

       前台服务与普通服务最大区别在于:他有一个正在运行的图标在系统状态栏显示,下拉通知栏可看到更加详细,类似通知。比 如:彩云天气,后台更新天气数据,在系统栏一直显示当前天气信息。

        与Notifiction比较类似,构建的notification并没有使用NotificationManager显示出来,而是调用startForeGround来使MyService变成一个前台服务,并在系统状态栏显示处出来。(PS:尽量整完整的代码,否则后面自己都搞不清楚了)

@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "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 contnet title").setContentText("This is contnet Text")
.setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi).build();
startForeground(1,notification);
}

       (二)使用IntentService

       服务默认在主线程中进行,若在服务中处理耗时逻辑,则很容易出现ANR。这是采用多线程编程技术,在服务的每个方法中开启一个子线程去处理耗时逻辑。服务执行完后自动停止,可以使用StopSelf方法。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand executed ");
new Thread(new Runnable() {
@Override
public void run() {
//处理具体逻辑
//服务执行完后自动停止,可以使用StopSelf方法
stopSelf();
}
});
return super.onStartCommand(intent, flags, startId);
}

AndroidIntentService主要用于解决程序员忘记开启线程,或者忘记调用stopSelf()方法。

《第一行代码》总结之网络、服务(五)_网络通信_07

与此同时,在主线程打印ID,并启动IntentService。

case R.id.btn_intentService:
Log.d("MainActivity", "Thread id is: "+Thread.currentThread().getId());
Intent intent_service = new Intent(this,MyIntentService.class);
startService(intent_service);
break;

10.6 服务最佳实践-完整的下载示例

      1.添加OkHttp的依赖, compile 'com.squareup.okhttp3:okhttp:3.4.1'

       2.定义回调接口,用于对下载过程中的各种状态进行监听和回调

public interface DownloadListener {
void onProgress(int progress);
void onSuccess();
void onFailed();
void onPaused();
void onCanceled();
}

     3.AsyncTask三个泛型参数第一个为字符串传给后台,第二个整数类型作为进度显示;第三个利用整形数据来反馈结果,4个整数常量来表示下载的状态,成功/失败/暂停/取消。并在构造函数中传入刚定义的DownloadListener参数。

     4.doInBackground中,从参数中获取URL,解析出文件名,指定下载到某目录下。看它是否存在,若存在读取字节数,后面实现断点续传功能。使用getContentLength来获取文件长度。成功与否可以看出来。接着使用OKhttp发送请求,addHeader指名从哪个字节开始续下载。接着使用Java文件流的方式从网络读取数据,并不断写入本地,一直下载结束为止。中间判断用户是否有取消或暂停操作。调用pubishProgress进行通知.

      5.onProgressUpdate中从参数中获取当前下载进度,若有变化使用onProgress更新进度。

      6.onPostExecute中根据参数中传入的下载状态进行回调各种接口方法。

public class DownLoadTask extends AsyncTask<String, Integer, Integer> {
//3.AsyncTask三个泛型参数第一个为字符串传给后台,第二个整数类型作为进度显示;第三个利用整形数据来反馈结果
//4个整数常量来表示下载的状态,成功/失败/暂停/取消。并在构造函数中传入刚定义的DownloadListener参数
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;

public DownloadListener listener;
private boolean isCanceled = false;
private boolean isPaused = false;

private int lastProgress;

public DownLoadTask(DownloadListener listener) {
this.listener = listener;
}

//4.doInBackground中,从参数中获取URL,解析出文件名,指定下载到某目录下。看它是否存在,若存在读取字节数,后面实现断点续传功能。
//使用getContentLength来获取文件长度。成功与否可以看出来。接着使用OKhttp发送请求,addHeader指名从哪个字节开始续下载。
//接着使用Java文件流的方式从网络读取数据,并不断写入本地,一直下载结束为止。中间判断用户是否有取消或暂停操作。调用pubishProgress
// 进行通知.
@Override
protected Integer doInBackground(String... params) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try {
long downloadLength = 0;//记载已下载的文件长度
String downloadUrl = params[0];
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
file = new File(directory + fileName);
if (file.exists()) {
downloadLength = file.length();
}
long contentLength = 0;
contentLength = getContentLength(downloadUrl);
if (contentLength == 0) {
return TYPE_FAILED;
} else if (contentLength == downloadLength) {
//已下载的字节与文字总字节相等,则说明已经下载完成了
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//断点下载,指定从哪个字节开始下载
.addHeader("RANGE", "bytes=" + downloadLength + "-")
.url(downloadUrl).build();
Response response = client.newCall(request).execute();
if (response != null) {
is = response.body().byteStream();
savedFile = new RandomAccessFile(file, "rw");
savedFile.seek(downloadLength);
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 + downloadLength) * 100 / contentLength);
publishProgress(progress);
}
}
response.body().close();
return TYPE_SUCCESS;
}
return null;
} catch (IOException 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;
}
}
//5.onProgressUpdate中从参数中获取当前下载进度,若有变化使用onProgress更新进度。
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
listener.onProgress(progress);
lastProgress = progress;
}
}

//6.onPostExecute中根据参数中传入的下载状态进行回调各种接口方法。
@Override
protected void onPostExecute(Integer integer) {
switch (integer){
case TYPE_SUCCESS:
listener.onSuccess();
break;
case TYPE_FAILED:
listener.onFailed();
break;
case TYPE_PAUSED:
listener.onPaused();
break;
case TYPE_CANCELED:
listener.onCanceled();
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;
}
}

     7.开始写服务,匿名类DownloadListener实例中实现onProgress、onSuccess等五个方法,getNotificaionManager用于构造显示下载进度的通知,notify方法用于触发通知。在success方法中,先关闭前台通知,再弹框通知下载成功。

     8.创建的DownloadBinder继承自Binder提供了startDowload、pauseDownload和cancelDownload方法。取消下载时注意将文件删除掉。

    9.getNotification方法构建通知,设置完setProgress用于设置进度。第一个参数是最大进度,第二个是当前进度。后端工作已完成,编写前端。


public class DownloadService extends Service {
public DownLoadTask downLoadTask;
private String downbloadurl;
private DownloadListener listener = new DownloadListener() {
@Override
public void onProgress(int progress) {
getNotificaionManager().notify(1, getNotification("Downloading...", progress));
}

@Override
public void onSuccess() {
downLoadTask = null;
//下载成功时将前台通知关闭,并创建一个下载成功的通知
stopForeground(true);
getNotificaionManager().notify(1, getNotification("Download success...", -1));
Toast.makeText(DownloadService.this, "尽情享受吧", Toast.LENGTH_SHORT).show();
}

@Override
public void onFailed() {
downLoadTask = null;
//下载失败将前台服务通知关闭,并创建一个下载失败的通知
stopForeground(true);
getNotificaionManager().notify(1, getNotification("Download Failed", -1));
Toast.makeText(DownloadService.this, "小哥哥好像下载失败了", Toast.LENGTH_SHORT).show();
}

@Override
public void onPaused() {
downLoadTask = null;
Toast.makeText(DownloadService.this, "你是不是点暂停了", Toast.LENGTH_SHORT).show();
}

@Override
public void onCanceled() {
downLoadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this, "取消了啊...", Toast.LENGTH_SHORT).show();
}
};
//9.getNotification方法构建通知,设置完setProgress用于设置进度。第一个参数是最大进度,第二个是当前进度。
// 后端工作已完成,编写前端。
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.setContentTitle(title);
builder.setContentIntent(pi);
if (progress > 0) {
//当progress大于0时需要显示下载进度
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
}

private NotificationManager getNotificaionManager() {
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}

private DownloadBinder mBinder = new DownloadBinder();

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
//8.创建的DownloadBinder继承自Binder提供了startDowload、pauseDownload和cancelDownload方法。取消下载时
// 注意将文件删除掉
class DownloadBinder extends Binder {
public void startDowload(String url) {
if (downLoadTask == null) {
downbloadurl = url;
downLoadTask = new DownLoadTask(listener);
downLoadTask.execute(downbloadurl);
startForeground(1, getNotification("Downloading", 0));
Toast.makeText(DownloadService.this, "下载着呢,别催我...", Toast.LENGTH_SHORT).show();
}
}

public void pauseDownload() {
if (downLoadTask != null) {
downLoadTask.pauseDownload();
}
}

public void cancelDownload() {
if (downLoadTask != null) {
downLoadTask.cancelDownload();
} else if (downbloadurl != null) {
//取消下载时将文件删除并将通知取消
String fileName = downbloadurl.substring(downbloadurl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
File file = new File(directory + fileName);
if (file.exists()) {
file.delete();
}
getNotificaionManager().cancel(1);
stopForeground(true);
Toast.makeText(DownloadService.this, "取消了...", Toast.LENGTH_SHORT).show();
}
}
}
}

     10.在MainActivity创建ServiceConnection匿名类,在onServiceConnected获取到DownloadBinder实例。这样便可以调用服务提供的各种方法了

     11.下来启动并绑定服务,启动服务以便于保证DownloadService一直在后台运行;绑定服务保证了MainActivity与DownLoadService一直通信。动态申请WRITE_EXTERNAL_STORAGE权限。

    12.点击事件,开始、结束、暂停及相应的点击事件。最后活动被销毁时,一定要记得对服务解除绑定,否则会引起内存泄漏。最后申明权限。

     注意:遇到问题不要慌,又不是解决不了。调试程序是一件痛苦并快乐的事。Enjoy it!