1. 程式人生 > 其它 >Android 版本更新------後臺服務和前臺通知欄實現

Android 版本更新------後臺服務和前臺通知欄實現

技術標籤:我的Android之路androidappapkjava

前言:最近公司專案版本需要把以前的版本更新程式碼更新一下,換成Service和前臺通知欄形式的,所以在此記錄一下。

目錄

1.檢測版本更新(省略)

2.建立Service開啟前臺通知欄,並且執行下載任務,將下載進度通知到通知欄,下載完成後進行安裝

完整的Service程式碼

Service所用到的變數補充

2.開啟一個透明InstallApkActivity執行安裝Apk操作

Activity程式碼

工具類程式碼

3.注意事項:因為24版本之後讀取檔案需要配置FileProvider,不然無法讀取到apk檔案

在res目錄下建一個xml資料夾,然後建立file_path.xml檔案

AndroidManifest.xml配置

OnlineFileProvider類程式碼


1.檢測版本更新(省略)

這一步一般是通過後臺介面對比當前APP版本是否低於伺服器版本,從而獲取最新版本的下載連結。這一步比較簡單所以省略了。

2.建立Service開啟前臺通知欄,並且執行下載任務,將下載進度通知到通知欄,下載完成後進行安裝

完整的Service程式碼

package com.qtz.online.service;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

import com.qtz.online.R;
import com.qtz.online.common.AppService;
import com.qtz.online.event_bus.DownloadApkMessage;
import com.qtz.online.mvp.activitys.InstallApkActivity;
import com.qtz.online.network.callback.ServiceDownloadObserver;
import com.qtz.online.network.client.DownLoadClient;
import com.qtz.online.utils.LogUtil;

import org.greenrobot.eventbus.EventBus;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.ResponseBody;

/**
 * @author Created by PengGuiChu on 2020/12/19 19:32.
 * @explain  版本更新服務
 */
public class DownloadApkService extends Service {
    private static final String TAG=DownloadApkService.class.getSimpleName();
    private String url;//下載
    private String fileDir;
    private String fileName;
    private NotificationManager notificationManager;
    private NotificationCompat.Builder notificationBuilder;
    private static final String name="download_apk";
    private static final String id="Download_APK";
    private static final int startForegroundId=777;
    private int downLoadProgress;
    @Override
    public void onCreate() {
        super.onCreate();
        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationBuilder = getNotificationBuilder(notificationManager,getString(R.string.version_upgrade),getString(R.string.downloading,"0%"));
        notificationBuilder.setProgress(100,0,false);
        startForeground(startForegroundId,notificationBuilder.build());
//        notificationManager.notify(2,notificationBuilder.build());
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent!=null){
            url=intent.getStringExtra("url");
            fileDir=intent.getStringExtra("fileDir");
            fileName=intent.getStringExtra("fileName");
            //該下載請求是我專案的下載請求,需要替換成你專案的下載檔案的請求
            downloadFile(DownLoadClient.getService(AppService.class).download(url), new ServiceDownloadObserver<ResponseBody>(fileDir,fileName) {
                @Override
                public void onDownloadStart() {

                }

                @Override
                public void onDownloading(long progress, long total) {
                    int down= (int) (progress*100/total);
                    if (down!=downLoadProgress){
                        downLoadProgress=down;
                        if (notificationBuilder!=null){
                            notificationBuilder.setContentText(getString(R.string.downloading,downLoadProgress+"%"));
                            notificationBuilder.setProgress(100, downLoadProgress,false);
                        }
                        if (notificationManager!=null){
                            notificationManager.notify(startForegroundId,notificationBuilder.build());
                        }
                    }
                }

                @Override
                public void onDownloadSuccess(ResponseBody responseBody, String filePath) {
                    LogUtil.d(TAG,"下載完成,開啟一個空白Activity執行安裝視窗,保證使用者在任何地方都能開啟安裝步驟");
//                    EventBus.getDefault().post(new DownloadApkMessage(0,filePath));
                    Intent intent=new Intent(getApplicationContext(), InstallApkActivity.class);
                    intent.putExtra("filePath",filePath);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);
                    stopForeground(true);
                    stopSelf();
                }

                @Override
                public void onDownloadError(String msg) {
                    LogUtil.d(TAG,"下載出錯");
                    EventBus.getDefault().post(new DownloadApkMessage(1,msg));
                    stopForeground(true);
                    stopSelf();
                }
            });
        }
        return super.onStartCommand(intent, flags, startId);
    }

    private NotificationCompat.Builder getNotificationBuilder(NotificationManager notificationManager, String title, String message) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(DownloadApkService.id, DownloadApkService.name,
                    NotificationManager.IMPORTANCE_LOW);
            //閃光燈
            channel.enableLights(false);
            //是否允許震動
            channel.enableVibration(false);
            //設定可繞過  請勿打擾模式
            channel.setBypassDnd(false);
            channel.setSound(null,null);
            notificationManager.createNotificationChannel(channel);
        }
        NotificationCompat.Builder notification = new NotificationCompat.Builder(this, DownloadApkService.id);
        notification.build().flags= Notification.FLAG_ONGOING_EVENT;
        notification.setContentTitle(title);
        notification.setDefaults(NotificationCompat.DEFAULT_VIBRATE);
        notification.setContentText(message);
        notification.setSmallIcon(R.mipmap.ic_launcher);
        notification.setAutoCancel(false);
        notification.setCategory(Notification.CATEGORY_PROGRESS);
        return notification;
    }

    protected void downloadFile(Observable<ResponseBody> downloadObservable, final ServiceDownloadObserver<ResponseBody> downloadObserver) {
        downloadObservable.subscribeOn(Schedulers.io())//請求網路 在排程者的io執行緒
                .observeOn(Schedulers.io()) //指定執行緒儲存檔案
                .doOnNext(downloadObserver::saveFile)
                .observeOn(AndroidSchedulers.mainThread()) //在主執行緒中更新ui
                .subscribe(downloadObserver);
    }
}

Service所用到的變數補充

<string name="version_upgrade">發現新版本</string>
<string name="downloading">下載中…%1$s</string>

2.開啟一個透明InstallApkActivity執行安裝Apk操作

Activity程式碼

package com.qtz.online.mvp.activitys;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.qtz.online.R;
import com.qtz.online.utils.InstallApkUtil;
import java.io.File;

public class InstallApkActivity extends AppCompatActivity {
    private InstallApkUtil installApkUtil;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_install_apk);
        String filePath = getIntent().getStringExtra("filePath");
        initInstallApkUtil(new File(filePath));
    }

    private void initInstallApkUtil(File file) {
        if (installApkUtil == null) {
            installApkUtil = new InstallApkUtil(InstallApkActivity.this, file.getAbsolutePath());
        }
        installApkUtil.installApk();
    }

    //更具使用者授權回撥執行安裝操作
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 101) {
            if (installApkUtil != null) {
                installApkUtil.installApk();
            }
        }else if (requestCode==102){
            finish();
        }
    }
}

工具類程式碼

package com.qtz.online.utils;

import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import com.qtz.online.BuildConfig;
import com.qtz.online.base.BaseActivity;
import com.qtz.online.view.ChoiceDialog;

import org.greenrobot.eventbus.EventBus;

import java.io.File;

/**
 * @author Created by PengGuiChu on 2020/12/19 15:53.
 * @explain
 */
public class InstallApkUtil {
    private AppCompatActivity baseActivity;
    private String apkPath;
    private ChoiceDialog choiceDialog;

    public InstallApkUtil(AppCompatActivity baseActivity, String apkPath) {
        this.baseActivity = baseActivity;
        this.apkPath=apkPath;
    }

    public void installApk() {
        if (Build.VERSION.SDK_INT >= 26) {
            //來判斷應用是否有許可權安裝apk
            boolean installAllowed = baseActivity.getPackageManager().canRequestPackageInstalls();
            //有許可權
            if (installAllowed) {
                //安裝apk
                install();
            } else {
//這裡彈出一個選擇對話方塊,提示使用者需要授權第三方安裝應用,需要自己實現
                if (choiceDialog==null){
                    choiceDialog = new ChoiceDialog(baseActivity, new ChoiceDialog.ClauseDialogCallBack() {
                        @Override
                        public void onExit() {
                            baseActivity.finish();
                        }

                        @Override
                        public void onAgree() {
                            Uri packageURI = Uri.parse("package:" + baseActivity.getPackageName());
                            Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
                            baseActivity.startActivityForResult(intent, 101);
                        }
                    }, "安裝應用需要開啟未知來源許可權,請去設定中開啟許可權", "許可權申請", "取消", "前往");
                }
                choiceDialog.show();
            }
        } else {
            install();
        }
    }

    public void install() {
        if (Build.VERSION.SDK_INT >= 24) {
            Uri apkUri = FileProvider.getUriForFile(baseActivity,
                    BuildConfig.APPLICATION_ID + ".myprovider", new File(apkPath));
            Intent installIntent = new Intent(Intent.ACTION_VIEW);
            installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
            baseActivity.startActivityForResult(installIntent,102);
        } else {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setDataAndType(Uri.parse("file://" + apkPath), "application/vnd.android.package-archive");
            baseActivity.startActivityForResult(intent,102);
        }
    }

}

3.注意事項:因為24版本之後讀取檔案需要配置FileProvider,不然無法讀取到apk檔案

在res目錄下建一個xml資料夾,然後建立file_path.xml檔案

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-cache-path path="apk" name="."/>
</paths>

因為我是把apk下載在了Android/Data/包名/cache/apk目錄下所以這樣配置,這裡需要根據你的具體情況進行配置

  • <root-path/> 代表裝置的根目錄new File("/");
  • <files-path/> 代表context.getFilesDir()
  • <cache-path/> 代表context.getCacheDir()
  • <external-path/> 代表Environment.getExternalStorageDirectory()
  • <external-files-path>代表context.getExternalFilesDirs()
  • <external-cache-path>代表getExternalCacheDirs()

AndroidManifest.xml配置

<provider
            android:name=".utils.OnlineFileProvider"
            android:authorities="${applicationId}.myprovider"
            android:exported="false"
            android:grantUriPermissions="true">

            <!-- 元資料 -->
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
        </provider>

OnlineFileProvider類程式碼

package com.qtz.online.utils;

import androidx.core.content.FileProvider;

/**
 * @author Created by PengGuiChu on 2020/12/19 16:39.
 * @explain
 */
public class OnlineFileProvider extends FileProvider {
}