把Android的下載更新做成後臺服務

做Android的版本更新,首先要有一個接口從網絡獲取最新版本,再根據最新版本的url,也就是下載鏈接進行下載安裝。總的流程就是這樣,這篇博客就不講如何獲取最新版本了,這個需要就跟寫接口的哥們好好交流一下了~

#####那麼我們開始吧!
一開始我做的這個功能是這樣的:(上圖!)

後來發現,這樣用戶體驗不是很好,需要等到進度條讀完才能安裝更新,這過程中什麼都不能幹,的確有點體驗差。如果將Apk文件的下載放置在後臺,並在通知欄以靜默下載的消失去展示,會不會更好呢?先來看一下成果:


顯然,效果好多了,產品經理再也不用擔心啦!接下來展開講訴一下實現過程。

實現

  1. 創建一個服務Service
    相信這一步大家都會,創建一個DownAPKService繼承自Service,寫完不要忘記在清單文件AndroidManifest.xml中註冊該服務。

DownAPKService.java

public class DownAPKService extends Service {
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

AndroidManifest.xml

 <service
            android:name=".service.DownAPKService" />
  1. 在Service生命週期方法中的具體實現
    這一步貼一下完整代碼,相信大家都能看得懂~
package com.xxx.xxx.service;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.os.Vibrator;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;

import xxx.xxx.logger.Logger;
import xxx.xxx.goldfields.R;

import org.xutils.common.Callback;
import org.xutils.http.RequestParams;
import org.xutils.x;

import java.io.File;
import java.text.DecimalFormat;

/**
 * @author lam
 * @date 2019/02/27
 */
public class DownAPKService extends Service {
    private final int NotificationID = 0x10000;
    private NotificationManager mNotificationManager = null;
    private NotificationCompat.Builder builder;

    // 文件保存路徑(如果有SD卡就保存SD卡,如果沒有SD卡就保存到手機包名下的路徑)
    private String APK_dir = "";

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        initAPKDir();// 創建保存路徑
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("onStartCommand");
        // 接收Intent傳來的參數:
        // 文件下載路徑
        String APK_URL = intent.getStringExtra("apk_url");
        Logger.i("DownAPKService:url=" + APK_URL);

        DownFile(APK_URL, APK_dir + "Club.apk");

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

    private void initAPKDir() {
        /**
         * 創建路徑的時候一定要用[/],不能使用[\],但是創建文件夾加文件的時候可以使用[\].
         * [/]符號是Linux系統路徑分隔符,而[\]是windows系統路徑分隔符 Android內核是Linux.
         */
        if (isHasSdcard())// 判斷是否插入SD卡
        {
            APK_dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/VersionChecker/"; // 保存到SD卡路徑下
        } else {
            APK_dir = getApplicationContext().getFilesDir().getAbsolutePath() + "/VersionChecker/"; // 保存到app的包名路徑下
        }
        File destDir = new File(APK_dir);
        if (!destDir.exists()) {// 判斷文件夾是否存在
            destDir.mkdirs();
        }
    }

    /**
     * @Description 判斷是否插入SD卡
     */
    private boolean isHasSdcard() {
        String status = Environment.getExternalStorageState();
        if (status.equals(Environment.MEDIA_MOUNTED)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param file_url    下載鏈接
     * @param target_name 保存路徑
     */
    private void DownFile(String file_url, String target_name) {
        RequestParams params = new RequestParams(file_url);
        params.setSaveFilePath(target_name);  //設置下載後的文件保存的位置
        params.setAutoResume(true);  //設置是否在下載是自動斷點續傳
        params.setAutoRename(true);  //設置是否根據頭信息自動命名文件
        x.http().get(params, new Callback.ProgressCallback<File>() {
            @Override
            public void onSuccess(File result) {
                System.out.println("文件下載完成");
                Intent installIntent = new Intent(Intent.ACTION_VIEW);
                System.out.println(result.getPath());
                Uri uri = Uri.fromFile(new File(result.getPath()));
                installIntent.setDataAndType(uri, "application/vnd.android.package-archive");
                installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                PendingIntent mPendingIntent = PendingIntent.getActivity(DownAPKService.this, 0, installIntent, 0);
                builder.setContentText("下載完成,請點擊安裝");
                builder.setContentIntent(mPendingIntent);
                mNotificationManager.notify(NotificationID, builder.build());
                // 震動提示
                Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                assert vibrator != null;
                vibrator.vibrate(250L);// 參數是震動時間(long類型)
                stopSelf();
                startActivity(installIntent);// 下載完成之後自動彈出安裝界面
                mNotificationManager.cancel(NotificationID);
            }

            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                System.out.println("文件下載失敗");
                mNotificationManager.cancel(NotificationID);
                Toast.makeText(getApplicationContext(), "下載失敗,請檢查網絡!", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onCancelled(CancelledException cex) {
                System.out.println("文件下載結束,停止下載器");
            }

            @Override
            public void onFinished() {
                System.out.println("文件下載完成");
            }

            @Override
            public void onWaiting() {
                System.out.println("文件下載處於等待狀態");
            }

            @Override
            public void onStarted() {
                Toast.makeText(getApplicationContext(), "開始後臺下載更新文件...", Toast.LENGTH_SHORT).show();
                System.out.println("開始下載文件");
                String id = "my_channel_01";
                String name = "我是渠道名字";
                mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                // 針對Android 8.0版本對於消息欄的限制,需要加入channel渠道這一概念
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {  //Android 8.0以上
                    NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
                    Log.i("DownAPKService", mChannel.toString());
                    mNotificationManager.createNotificationChannel(mChannel);
                    builder = new NotificationCompat.Builder(getApplicationContext());
                    builder.setSmallIcon(R.drawable.icon);
                    builder.setTicker("正在下載新版本");
                    builder.setContentTitle(getApplicationName());
                    builder.setContentText("正在下載,請稍後...");
                    builder.setNumber(0);
                    builder.setChannelId(id);
                    builder.setAutoCancel(true);
                } else {    //Android 8.0以下
                    builder = new NotificationCompat.Builder(getApplicationContext());
                    builder.setSmallIcon(R.drawable.icon);
                    builder.setTicker("正在下載新版本");
                    builder.setContentTitle(getApplicationName());
                    builder.setContentText("正在下載,請稍後...");
                    builder.setNumber(0);
                    builder.setAutoCancel(true);
                }

                mNotificationManager.notify(NotificationID, builder.build());
            }

            @Override
            public void onLoading(long total, long current, boolean isDownloading) {
                System.out.println("文件下載中");
                int x = Long.valueOf(current).intValue();
                int totalS = Long.valueOf(total).intValue();
                builder.setProgress(totalS, x, false);
                builder.setContentInfo(getPercent(x, totalS));
                mNotificationManager.notify(NotificationID, builder.build());
                //當前進度和文件總大小
                Log.i("DownAPKService", "current:" + current + ",total:" + total);
            }
        });

    }

    /**
     * @param x     當前值
     * @param total 總值
     *              [url=home.php?mod=space&uid=7300]@return[/url] 當前百分比
     * @Description 返回百分之值
     */
    private String getPercent(int x, int total) {
        String result = "";// 接受百分比的值
        double x_double = x * 1.0;
        double tempresult = x_double / total;
        // 百分比格式,後面不足2位的用0補齊 ##.00%
        DecimalFormat df1 = new DecimalFormat("0.00%");
        result = df1.format(tempresult);
        return result;
    }

    /**
     * @return
     * @Description 獲取當前應用的名稱
     */
    private String getApplicationName() {
        PackageManager packageManager = null;
        ApplicationInfo applicationInfo = null;
        try {
            packageManager = getApplicationContext().getPackageManager();
            applicationInfo = packageManager.getApplicationInfo(getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            applicationInfo = null;
        }
        String applicationName = (String) packageManager.getApplicationLabel(applicationInfo);
        return applicationName;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopSelf();
    }
}

  1. 注意事項

(1)在onStartCommand中需要用到的url是通過Intent獲取的,在開啓服務的Activity或者Fragment中需要傳遞這個值:

Intent intent = new Intent(context, DownAPKService.class);
intent.putExtra("apk_url", downUrl);
context.startService(intent);

(2)實現下載的方法中用到的是XUtils這個網絡框架,大家各持所需,用自己順手的網絡框架即可。之所以使用XUtils是因爲它很好的幫我們實現了斷點續傳的功能。
(3)上述代碼中關於Android通知欄Notification的用法,在Android O(8.0)版本之後新增了渠道的概念,這一部分知識點我會開另外一篇博客着重講解。我們只需要知道這個部分需要做版本兼容的適配。
(4)最後,大家別忘記在onDestroy中關閉服務:

stopSelf();

至此,這個後臺服務去更新App的功能就已經實現了,你可以在你的真機或者模擬器上看看效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章