====================================
====== 第十章:后台默默的劳动者 — 探究服务 ======
====================================
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();
}
}