Android第一行代码读书笔记 - 第十章

2019-11-20  本文已影响0人  武当霍元甲

====================================

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

====================================

10.1 服务是什么

服务Service是Android中实现后台程序运行的解决方案。

需要注意,服务并不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。

实际上,服务并没有自动开启线程,所有的代码都是默认运行在主线中当中。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。

10.2 Android多线程编程。

10.2.1 线程的基本用法

1、定义一个线程只需要新建一个类继承自Thread,然后重写父类的run()方法,并在run方法里面执行耗时逻辑即可

class MyThread extends Thread {

@Override

public void run() {

// 处理具体逻辑

}

}

启动线程:new MyThread().start();

2、由于使用集成的方式来创建线程耦合性有点高,更多的时候我们会选择使用Runnable接口的方式来定义一个线程:

class MyThread implements Runnable {

@Override

public void run() {

// 处理具体的逻辑

}

}

启动线程方法:

MyThread myThread = new MyThread();

new Thread(myThread).start(); // Thread的构造函数接收一个Runnable参数,我们MyThread正是一个实现了Runnable接口的对象。这样,run()方法中的代码就会在子线程中运行了。

也可以这样写:

new Thread(new Runnable() {

@Override

public void run() {

// 处理具体的逻辑

}

}).start();

10.2.2 在子线程中更新UI

新建一个AndroidThreadTest项目:

1、修改activity_main.xml

<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<Button

android:id=“@+id/change_text”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Change Text” />

<TextView

android:id=“@+id/text”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_centerInParent=“true”

android:text=“Hello World”

android:textSize=“20sp” />

</RelativeLayout>

2、修改MainActivity的代码

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private TextView text;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

text = (TextView) findViewById(R.id.text);

Button changeText = (Button) findViewById(R.id.change_text);

changeText.setOnClickListener(this);

}

@Override

public void onClick(View v) {

swith (v.getId()) {

case R.id.change_text : {

new Thread(new Runnable() {

@Override

public void run() {

text.setText(“Nice to meet you”);

}

}).start();

default:

break;

}

}

}

}

我们运行一下,发现崩溃了。因为UI操作必须要在主线程!!

对于这种情况,Android提供了一套异步消息处理机制:

修改MainActivity代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

public static final int UPDATE_TEXT = 1; // 用于表示更新TextView这个动作

private TextView text;

private Handle handler = new Handler() {

public void handlerMessage(Message msg) {

switch (msg.what) {

case UPDATE_TEXT:

// 在这里可以进行UI操作

text.setText(“Nice to meet you”);

break;

default:

break;

}

});

@Override

public void onClick(View v) {

switch (v.getId()) {

new Thread(new Runnable() {

@Override

public void run() {

Message message = new Message();

message.what = UPDATE_TEXT;

handler.sendMessage(message); // 将Message对象发送出去

}

}).start();

break;

default:

break;

}

}

}

10.2.3 解析异步消息处理机制

Android中的异步消息处理主要由四个部分组成:Message、Handler、MessageQueue、Looper

1、Message:

Message是在线程之间传递的消息,可以携带少量信息,除了what字段,还可以使用arg1、arg2携带整形数据,使用obj字段携带Object对象

2、Handler

Handler是处理者的意思,主要用于发送和处理消息。发送信息使用sendMessage()方法,发出的消息经过一些列辗转之后,最终会传递到handleMessage()方法中。

3、MessageQueue

MessageQueue是消息队列的意思,主要用于存放所有通过Handler发送的消息,这部分消息一直存在于消息队列中,等待被处理。每个线程都只会有一个MessageQueue对象。

4、Looper

Looper是每个线程中的MessageQueue的管家,调用了Looper的loop()方法后,就会进入到一个无线循环当中,当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程都只会有一个Looper对象。

原理:我们在主线程创建了一个Handler对象,并重写了handlerMessage()方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理的消息,最后分发回Handler的handleMessage()方法中,由于Handler是在主线程中创建的,所以此时handlerMessage()方法中的代码也会在主线程中运行。

原理图如p346

10.2.4 使用AsyncTask

AsyncTask可以十分简单的从子线程切换到主线程。当然,AsyncTask背后的实现原理也是基于异步消息处理机制的,只不过Android帮我们做了很好的封装。

AsyncTask是一个抽象类,我们想使用它,就必须要创建一个类继承它。继承时我们可以为AsyncTask执行三个泛型参数:

1、Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用

2、Progress:后台任务执行时,如果需要在界面上显示当前进度,则使用这里的泛型作为进度单位

3、Result:当任务执行完毕时,如果需要对结果进行返回,这里指定的泛型作为返回值类型

因此,一个简单的自定义AsyncTask可以写成如下:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

}

第一泛型参数指定为Void,表示执行AsyncTask时候不需要传入参数给后台任务

第二泛型参数为Integer,表示使用整形数据作为进度显示单位

第三泛型参数为Boolean,表示使用布尔类型数据来反馈结果。

我们还需要重写AsyncTask的几个方法才能完成对任务的定制:需要重写的方法有四个:

1、onPreExecute()

开始执行前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等

2、doInBackground(Params…)

这个方法中的所有代码都会在子线程中执行,我们应该在这里处理所有的耗时操作。

3、onProgerssUpdate(Progress…)

当在后台任务doInBackground方法中调用了publishProgress(Progress…)方法后,onProgressUpdate(Progress…)方法很快会被调用,该方法中携带的参数就是在后台任务重传递过来的。在这个方法可以进行UI操作。

4、onPostExecute(Result)

当在后台任务执行完毕(onPreExecute())方法通过return语句进行返回时,这个方法会被调用。返回的数据会作为参数传递到此方法中。可以利用返回的数据进行一些UI操作。

以下是一个比较完整的自定义AsyncTask:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

@Override

protected void onPreExecute() {

progressDialog.show(); // 显示进度对话框

}

@Override

protected Boolean doInBackground(Void…params) {

try {

while(true) {

int downloadPercent = doDownload(); // 这是一个虚构的方法

// 传递进度条

publishProgress(downloadPercent);

if (downloadPercent >= 100) {

break;

}

}

} catch (Exception e) {

// 当执行到return,onPostExecute()方法会被调用

return false;

}

// 当执行到return,onPostExecute()方法会被调用

return true;

}

@Override

protected void onProgressUpdate(Integer… values) {

// 在这里更新下载进度

progerssDialog.setMessage(“Downloaded ” + values[0] + “%”);

}

@Override

protected void onPostExecute(Boolean result) {

progressDialog.dismiss(); // 关闭进度对话框

// 在这里提示下载结果

if (result) {

Toast.makeText(context, “Download succeeded”, Toast.LENGTH_SHORT).show();

} else {

Toast.makeText(context, “Download failed”, Toast.LENGTH_SHORT).show();

}

}

}

想要启动这个任务,只需要 new DownloadTask().execute()即可。

方便了很多,我们并不需要去考虑什么异步消息处理机制,也不需要专门使用一个Handler来发送和接收消息,只需要调用一下publishProgerss()方法,就可以轻松从子线程切换到UI线程。

10.3 服务的基本用法:

10.3.1 定义一个服务:

新建一个项目ServiceTest

右键com.example.servicetest -> New -> Service -> Service,命名为MyService,Enable和Exported都勾选

Enable表示其否启用

Exported表示是否允许除了当前程序之外的其他程序访问此服务

然后观察MyService的代码

public class MyService extends Service {

public MyService() {

}

// 此方法为抽象方法,必须要在子类中实现(下一小节再讲解)

@Override

public IBinder onBind(Intent intent) {

throw new UnsupportOperationException(“Not yet implement”);

}

// 现在重写Service的另外一些方法

// 服务创建的时候调用

@Override

public void onCreate() {

super.onCreate();

}

// 每次服务启动的时候调用

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

return super.onStartCommand(intent, flags, startId);

}

@Override

public void onDestroy() {

super.onDestroy();

}

}

Android四大组件的特点,每一个服务也都需要在AndroidManifest.xml文件中注册才能生效。但是Android Studio已经帮我们完成了这个操作。

<manifest xmlns:android=“http://schemas.android.com/apk/res/android

package=“com.example.servicetest” >

<application

android:allowBackup=“true”

android:icon=“@mipmap/ic_launcher”

android:label=“@string/app_name”

android:supportsRtl=“true”

android:theme=“@style/AppTheme” >

<service

android:name=“.MyService”

android:enabled=“true”

android:exported=“true” >

</service>

</application>

</manifest>

10.3.2 启动和停止服务:

启动和停止服务主要是借助Intent来实现:

1、修改activity_main.xml代码
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android

android:orientation=“vertical”

android:layout_width=“match_parent”

android:layout_height=“match_parent” >

<Button

android:id=“@+id/start_service“

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Start Service” />

<Button

android:id=“@+id/stop_service”

android:layout_width=“match_parent”

androd:layout_height=“wrap_content”

android:text=“Stop Service” />

</LinearLayout>

2、修改MainActivity代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedIntanceState);

setContentView(R.layout.activity_main);

Button startService = (Button) findViewById(R.id.start_service);

Button stopService = (Button) findViewById(R.id.stop_service);

startService.setOnClickListener(this);

stopService.setOnClickListener(this);

}

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.start_service:

Intent startIntent = new Intent(this, MyService.class);

startService(startIntent); // 启动服务

break;

case R.id.stop_service:

Intent stopIntent = new Intent(this, MyService.class);

stopService(stopIntent);

break;

default:

break;

}

}

}

startService()方法和stopService()方法都是定义在Context类中的。

主要在MyService的任何一个位置调用stopSelf()方法,就可以让这个服务停止下来。

10.3.3 活动与服务进行通讯

启动了服务之后,活动与服务基本就没什么关系了:我们在活动里调用了startService()方法来启动MyServiece这个服务,然后MyService的onCreate()和onStartCommand()方法就会得到执行。之后服务就会一直处于运行状态,但是具体运行的是什么逻辑,活动就控制不了了。

这时候就需要用到了onBind()方法了。

现在我们需要在活动中可以决定何时开始下载,以及随时查看下载进度(需要创建一个专门的Binder对象来对下载功能进行管理)

public class MyService extends Service {

private DownloadBiinder extends Binder {

public void startDownload() {

Log.d(“MyService”, “startDownload executed”);

}

public int getProgerss() {

Log.d(“MyService”, “getProgerss executed”);

return 0;

}

@Override

public IBinder onBind(Intent intent) {

return mBinder;

}

}

接着,在MyService中创建了DownloadBinder的实例,然后在onBind()方法里反悔了这个实例,这样MyService中的工作就全部完成了。

修改activity_main.xml

<LinearLayout xmlns:android:”http://schemas.android.com/apk/res/android

android:orientation=“vertical”

android:layout_height=“match_parent”

android:layout_height=“match_parent” >

<Button

android:id=“@+id/bind_service”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Bind Service” />

<Button

android:id=“@+id/unbind_service”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Unbind Service” />

<LinearLayout>

上面两个按钮代表绑定服务和解绑服务。这个动作由活动去操作。当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了。修改MainActivity代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private MyService.DownloadBinder downloadBinder;

// 匿名类,创建一个connection

private ServiceConnection connection = new ServiceConnection() {

@Override

public void onServiceDisconnected(ComponentName name) {

// 当活动与服务解绑的时候调用

}

@Override

public void onServiceConnected(ComponentName name, IBInder service) {

// 当活动与服务绑定的时候调用

downloadBinder = (MyService.DownloadBinder) service;

downloadBinder.startDownload();

downloadBinder.getProgerss();

}

};

@Overrde

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layou.activity_main);

Button bindService = (Button) findViewById(R.id.bind_service);

Button unbindService = (Button) findViewById(R.id.unbind_service);

bindService.setOnClickListener(this);

unbindService.setOnClickLisener(this);

}

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.bind_service:

Intent bindIntent = new Intent(this, MyService.class);

bindService(bindIntent, connection, BIND_AUTO_CREATE); // 绑定服务,MyService的onCreate方法调用

break;

case R.id.unbind_service:

unbindService(connection); // 解绑服务

break;

default:

break;

}

}

10.4 服务的生命周期:

包含onCreate()、onStartCommand()、onBind()、onDestroy()方法。

一旦在项目任何位置调用了Context的startService()方法,相应的服务就会启动起来。并调用服务的onStartCommand()方法。如果这个服务之前没有创建过,onCreate()方法会先与onStartCommand()方法执行。服务启动之后就会保持运行状态,直到stopService或者stopSelf()方法被调用。注意:虽然每调用一次startService()方法,onStartCommand()就会执行一次。但是实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需要调用一次stopService()或stopSelf()方法,服务就会停止下来。

另外,可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind(0方法。类似的,如果这个服务之前还没有创建过,onCreate()会吸纳与onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和服务进行通讯了。只要调用方和服务之间的链接没有断开,服务就会一直保持运行状态。

当调用了startService()方法后,又调用stopService()方法,这时服务中的onDestroy()方法会执行,表示服务已经销毁了。类似的,当调用了bindService()方法后,又调用了unbindService方法,onDestroy()方法也会执行。有一种情况:我们对一个服务调用了startService(0方法,又调用了bindService()方法。这种时候,按照Android系统的机制,一个复苏只要被启动或者绑定之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能销毁。所以,这种情况下要同时调用stopService和unbindServicee()方法,onDestroy()方法才会执行。

10.5.1 使用前台服务:

服务几乎都是在后台运行的,但是服务的优先级比较低,当系统出现内存不足情况,就有可能会收掉正在后台运行的服务。所以我们可以考虑使用前台服务。前台服务特点是:它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏可以看到更加详细的信息,非常类似通知。有些项目比较特殊会要求必须使用前台服务,比如天气预报类应用。

创建一个前台服务:

1、修改MyService的代码:

public class MyService extends Service {

@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 NotificatioinCompat.Builder(this)

.setContentTitle(“This is content title”)

.setContentText(“This is content text”)

.setWhen(System.currentTimeMillis())

.setSmallIcon(R.mipmap.ic_launcher)

.setLargeIcon(BitmpaFactory.decodeResource(getResources(), R.mipmap.ic_launcher))

.setContentIntent(pi)

.build();

startForeground(1, notification);

}

}

上面构建出一个Notification对象后,并没有使用NotificaitonManager将通知显示出来,而是调用了startForeground()方法。第一个参数类似于通知id,第二个参数则是构建出的Notificaiton对象,调用startForeground方法会让Service变成一个前台服务,并在系统状态栏显示出来。

10.5.2 使用IntentService

服务中的代码默认都是运行在主线程当中的,如果直接在服务里处理一些耗时的逻辑,就很容易出现ANR(Applicatiion Not Responding)的情况,所以这个时候就需要用到android的多线程编程技术了。我们应该在服务的每个具体的方法里开启一个子线程。然后在这里去处理那些耗时的逻辑。

public class MyService extends Service {

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

new Thread(new Runnable() {

@Override

public void run() {

// 处理具体的逻辑

// 处理完之后关闭

stopSelf()

}

}).start();

return super.onStartCommand(intent, flags, startId);

}

}

这种服务一旦处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来。

由于总有程序员会忘记开启子线程,或者忘记调用stopSelf()方法。为了简约的创建一个异步的,会自动停止的服务,Android专门提供了一个IntentService类。

public class MyIntentService extends IntentService {

public MyIntentService() {

super(“MyIntentService”); // 调用父类的有参构造函数

}

@Override

protected void onHandleIntent(Intent intent) {

// 打印当前线程的id

Log.d(“MyIntentService”, “Thread id is ” + Thread.currentThread().getId());

// 这里可以处理一些具体的逻辑,并且不用担心ANR的问题,因为这个方法已经是子线程中运行的

}

@Override

public void onDestroy() {

super.onDestroy();

Log.d(“MyIntentService”, “onDestroy executed”);

}

}

上面的IntentService在运行结束之后是会自动停止的。

接下来修改activity_main.xml的代码,加入一个用于启动MyIntentService这个服务的按钮

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/androd

android:orientation=“vertical”

android:layout_height=“match_parent”

android:layout_width=“match_parent” >

<Button

android:id=“@+id/start_intent_service”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:text=“Start IntentService” />

</LinearLayout>

修改MainActivity的代码:

public class MainActivity extends AppCompatActivity implements View.OnClickLisener {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Button startIntentService = (Button) findViewById(R.id.start_intent_service);

startIntentService.setOnClickLisener(this);

}

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.start_intent_service:

// 打印主线程的id

Log.d(“MainActivity”, “Thread id id ” + Thread.currentThread().getId(0);

Intent intentService = new Intent(this, MyIntentService.class);

startService(intentService);

break;

default:

break;

}

}

}

其实发现,IntentService的用法和普通的服务没有什么两样

最后不要忘记,服务都需要再AndroidManifest.xml中注册:

<manifest xmlns:android=“http://schemas.android.com/apk/res/android

package=“com.example.servicetest” >

<application

android:allowBackup=“true”

android:icon=“@mipmap/ic_launcher”

android:label=“@string/app_name”

android:supportsRtl=“true”

android:theme=“@style/AppTheme” >

<service android:name=“.MyIntentService” />

</application>

</manifest>

10.6 服务的最佳实践 —> 完整版的下载示例

创建一个ServiceBestPractice项目

1、添加好依赖库:编辑app/build.gradle文件

dependencies {

compile fileTree(dir: ‘lib’, include: [‘*.jar’])

compile ‘com.android.support:appcompat-v7:24.2.1’

testCompile ‘junit:junit:4.12’

compile ‘com.squareup.okhttp3:okhttp:3.4.1’ // 添加OkHttp的依赖

}

2、定义一个回调接口,用于对下载过程中的各种状态进行监听和回调:新建一个DownloadListener接口

public interface DownloadListener {

void onProgerss(int progress);

void onSuccess();

void onFailed();

void onPause();

void onCancled();

}

3、编写下载功能,使用AsyncTask来实现:

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 lastProgerss;

// 构造函数

public DonwloadTask(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 = param[0]; // 从参数中获取下载的URL地址

String fileName = downloadUrl.substring(downloadUrl.lastIndexOf(“/”)); // 从参数中解析出下载的文件名

// 将文件下载在这个Environment.DIRECTORY_DOWNLOADS目录下。也就是SD卡的download目录

String directory = Environment.getExternalStoragePublicDirectory(Environmen.DIRECTORY_DOWNLOADS).getPath();

file = new FIle(directory + fileName);

if (file.exists()) {

// 如果已经存在了这个文件,则读取已下载的字节数,这样就可以断点续传了

downloadedLength = file.length();

}

long contentLength = getContentLength(downloadUrl);

if (contentLength = 0) {

// 如果文件大小为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_PAUSE;

} else {

total += len;

savedFile.write(b, 0, len);

// 计算已下载的百分比

int progress = (int) ((total + downloadLength) * 100 / conentLength);

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;

}

// 用于在界面上更新具体的下载进度

@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_SUCCESS:

listener.onSuccess();

break;

case TYPE_FAILED:

listener.onFailed();

break;

case TYPE_PAUSE:

listener.onPaused();

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 reqeust = 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;

}

}

首先看一下AsyncTask中的三个泛型参数:

参数1、String,表示在执行AysncTask的时候需要传入一个字符串给后台任务

参数2、Integer,表示使用整形数据来作为进度显示单位

参数3、Integer,表示使用整形数据来反馈执行结果。

4、为了保证DownloadTask可以一直在后台运行,我们还需要创建一个下载的服务。新建DownloadService

public class DownloadService extends Service {

private DownloadTask downloadTask;

private String downloadUrl;

private DownloadListener listener = new DownloadListener() {

@Override

public void onProgerss(int progress) {

getNotificationManager().notify(1, getNotification(“Downloading…”, progress));

}

@Override

public void onSuccess() {

downloadTask = null;

// 下载成功时将前台服务通知关闭,并创建一个下载成功的通知

stopForeground(true);

getNotificationManger().notify(1, getNotification(“Download Success”, -1));

Toast.makeText(DownloadService.this, “Download Success”, Toast.LENGTH_SHORT).show();

}

@Override

public void onFailed() {

downloadTask = null;

// 下载失败时将前台服务关闭,并创建一个下载失败的通知

stopForeground(true);

getNotificationManger().notify(1, getNotification(“Download Failed”, -1));

Toast.makeText(DownloadService.this, “Download Failed”, Toast.LENGTH_SHORT).show();

}

@Override

public void onPause() {

downloadTask = null;

Toast.makeText(DownloadService.this,”Paused”, Toast.LENGTH_SHORT).show();

}

@Override

public void onCanceled() {

downloadTask = null;

stopForground(true);

Toast.makeText(DownloadServie.this, “Canceled”, Toast.LENGTH_SHORT).show();

}

};

// 定义内部私有类

private 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);

downloadTask.execute(downloadUrl);

startForeground(1, getNotification(“Downloading…”, 0));

Toast.makeText(DownloadService.this, “Downloading…” Toask.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.make(DownloadService.this, “Cancel”, Toast.LENGTH_SHORT).show();

}

}

}

private NotificaitonManager getNotificationManager() {

return (NotificationManager) getSystemService(NOTIFICATION_SERVIC);

}

private Ntification geeNotificaiton(String titl, int progress) {

Intent intnet - new Intent(this, MainActivity.class);

PendingIntent pi = PendingIntent.getctivity(this, 0, intent, 0);

NotificationCompat.Builder builder = new NotificaiotnCompat.Builder(this);

builder.setSmallIcon(R.mipmap.ic_launcher);

builder.setLargeIcon(BitmapFactory.decodePesource(getResources(), R.mipmap.ic_laucher));

builder.setContentIntent(pi);

builder.setContentTItle(title);

if (progress > 0) {

// 当progress大于或等于0时才需要显示下载进度

builder.setContentText(progress + “%”);

builder.setProgress(100, progress, false);

}

return builder.build();

}

}

上一篇下一篇

猜你喜欢

热点阅读