美文网首页
Android第一行代码读书笔记 - 第十章

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

作者: 武当霍元甲 | 来源:发表于2019-11-20 13:50 被阅读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();

    }

    }

    相关文章

      网友评论

          本文标题:Android第一行代码读书笔记 - 第十章

          本文链接:https://www.haomeiwen.com/subject/oodoictx.html