Android uses Service+OKHttp to implement application background detection and update

Import OkHttp dependency

    implementation 'com.squareup.okhttp3:okhttp:3.14.4'
    implementation 'com.squareup.okio:okio:1.17.2'

Jurisdiction

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
    <!--Pop up system dialog box-->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

To configure

public class UpdateConfig {

    //File exists and is complete
    public static final int FILE_EXIST_INTACT = 1;
    //file does not exist
    public static final int FILE_NOT_EXIST = 2;
    //Incomplete file
    public static final int FILE_NOT_INTACT = 3;
    //File incomplete delete file exception
    public static final int FILE_DELETE_ERROR = 4;

    //Exception in getting version information
    public static final int CLIENT_INFO_ERROR = 5;

    //Which dialog box needs to pop up
    public static final int DIALOG_MUST = 6;
    public static final int DIALOG_CAN = 7;

    //Download exception
    public static final int DOWNLOAD_ERROR = 8;

    //Installation exception
    public static final int INSTALL_ERROR = 9;
}

Custom Service

public class UpdateService extends Service {

    public static boolean isRunning = false;
    public static String checkUrl = "";

    //current version
    private int versionCode = -1;
    //error message
    private String error_msg = "";
    //Update address
    private String updateUrl = "";
    //Update description
    private String description = "";

    //File path
    private String filePath = "";
    //File name
    private String appName = "";

    //Target file
    private File targetFile;

    private static OkHttpClient client = getClient();

    private Dialog mDialog;
    private ProgressDialog mProgressDialog;

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            int code = msg.what;
            switch (code) {
                case UpdateConfig.FILE_EXIST_INTACT:
                    Log.i("EricLog", "File integrity requires installation");
                    UpdateFile.installApp(targetFile, getApplicationContext(), handler);
                    break;
                case UpdateConfig.FILE_NOT_EXIST:
                    Log.i("EricLog", "file does not exist");
                    mProgressDialog = UpdateDialog.durationDialog(getApplicationContext());
                    mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
                    mProgressDialog.show();
                    UpdateFile.DownloadAsync dFne = new UpdateFile.DownloadAsync(targetFile, listener);
                    dFne.execute(updateUrl);
                    break;
                case UpdateConfig.FILE_DELETE_ERROR:
                    Log.i("EricLog", "File deletion exception");
                    error_msg = "File deletion error";
                    showDialog(DialogType.ERROR);
                    break;
                case UpdateConfig.FILE_NOT_INTACT:
                    Log.i("EricLog", "Incomplete file");
                    mProgressDialog = UpdateDialog.durationDialog(getApplicationContext());
                    mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
                    mProgressDialog.show();
                    UpdateFile.DownloadAsync dFni = new UpdateFile.DownloadAsync(targetFile, listener);
                    dFni.execute(updateUrl);
                    break;
                case UpdateConfig.DIALOG_MUST:
                    Log.i("EricLog", "Pop up the update required dialog box");
                    showDialog(DialogType.MUST);
                    break;
                case UpdateConfig.DIALOG_CAN:
                    Log.i("EricLog", "Pop up the update dialog box");
                    showDialog(DialogType.CAN);
                    break;
                case UpdateConfig.CLIENT_INFO_ERROR:
                    Log.i("EricLog", "Connection exception");
                    error_msg = "Get version exception,Please check the network";
                    showDialog(DialogType.ERROR);
                    break;
                case UpdateConfig.DOWNLOAD_ERROR:
                    Log.i("EricLog", "Download exception");
                    disProgress(false);
                    error_msg = "Download error,Please download again";
                    showDialog(DialogType.ERROR);
                    break;
                case UpdateConfig.INSTALL_ERROR:
                    Log.i("EricLog", "Installation exception");
                    error_msg = "Installation error, please install manually";
                    showDialog(DialogType.ERROR);
                    break;
            }
        }
    };

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("EricLog", "Service startup");
        filePath = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + File.separator + "ChilinApp/";
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!isRunning) {
            isRunning = true;

            versionCode = getVersionCode(getApplicationContext());
            String versionName = getVersionName(getApplicationContext());
            if (versionCode == -1 || TextUtils.isEmpty(versionName)){
                handler.sendEmptyMessage(UpdateConfig.CLIENT_INFO_ERROR);
            }else {
                if (UpdateCheck.isNetWorkConnected(getApplicationContext())
                        && UpdateCheck.isNetWorkAvailable(getApplicationContext())) {
                    UpdateCheck.checkVersion(client,
                            checkUrl,
                            versionName,
                            getPackageName(),
                            result);
                } else {
                    handler.sendEmptyMessage(UpdateConfig.CLIENT_INFO_ERROR);
                }
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("EricLog", "Service destruction");
        isRunning = false;
    }

    /**
     * Get version number
     * @return
     */
    public static int getVersionCode(Context context){
        PackageManager manager = context.getPackageManager();
        PackageInfo info;
        try {
            info = manager.getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            return -1;
        }
    }

    public static String getVersionName(Context context){
        PackageManager manager = context.getPackageManager();
        PackageInfo info;
        try {
            info = manager.getPackageInfo(context.getPackageName(), 0);
            return info.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            return "";
        }
    }

    private static OkHttpClient getClient(){
        return new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .build();
    }

    /**
     * Check for updates
     */
    private void checkUpdate(UpdateBean info){
        int targetCode = info.getData().getBuild();
        int isCompulsory = info.getData().getIsCompulsory();
        updateUrl = info.getData().getUrl();
        appName = info.getData().getApplication().getName();
        description = info.getData().getApplication().getDescription();
        String mPath = filePath + appName + ".apk";
        Log.i("EricLog", "Target file:"+mPath);
        targetFile = new File(mPath);
        if (TextUtils.isEmpty(description)){
            description = "Fixed several bug";
        }
        if (versionCode == targetCode){
            stopSelf();
        }else {
            if (versionCode < targetCode && isCompulsory == 0){
                handler.sendEmptyMessage(UpdateConfig.DIALOG_CAN);
            }else if (versionCode < targetCode && isCompulsory == 1){
                handler.sendEmptyMessage(UpdateConfig.DIALOG_MUST);
            }
        }
    }

    /**
     * Which dialog should be displayed
     * @param type
     */
    private void showDialog(DialogType type){
        if (type == DialogType.MUST){
            mDialog = UpdateDialog.mustUpdate(getApplicationContext(),
                    description,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            UpdateFile.CheckFile checkFile = new UpdateFile.CheckFile(
                                    targetFile, updateUrl, handler);
                            checkFile.execute();
                        }
                    });
        }else if (type == DialogType.CAN){
            mDialog = UpdateDialog.canUpdate(getApplicationContext(),
                    description,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            if (which == DialogInterface.BUTTON_POSITIVE){
                                UpdateFile.CheckFile checkFile = new UpdateFile.CheckFile(
                                        targetFile, updateUrl, handler);
                                checkFile.execute();
                            }else if (which == DialogInterface.BUTTON_NEGATIVE){
                                dismiss();
                            }
                        }
                    },
                    new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialog) {
                            dismiss();
                        }
                    });
        }else if (type == DialogType.ERROR){
            mDialog = UpdateDialog.errorDialog(getApplicationContext(),
                    error_msg,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dismiss();
                        }
                    });
        }
        mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        mDialog.show();
    }

    private void dismiss(){
        if (mDialog != null && mDialog.isShowing()){
            stopSelf();
            mDialog.dismiss();
        }
    }

    private void disProgress(boolean finishService){
        if (finishService){
            stopSelf();
        }
        if (mProgressDialog != null && mProgressDialog.isShowing()){
            mProgressDialog.dismiss();
        }
    }

    private UpdateCheck.CheckVersionResult result = new UpdateCheck.CheckVersionResult() {
        @Override
        public void success(UpdateBean info) {
            if (info != null && info.getData() != null) {
                checkUpdate(info);
            }else {
                handler.sendEmptyMessage(UpdateConfig.CLIENT_INFO_ERROR);
            }
        }

        @Override
        public void fail(int code) {
            Log.e("EricLog", "Code = " + code);
            handler.sendEmptyMessage(UpdateConfig.CLIENT_INFO_ERROR);
        }

        @Override
        public void error(Throwable throwable) {
            Log.e("EricLog", "Error = " + throwable.getMessage());
            handler.sendEmptyMessage(UpdateConfig.CLIENT_INFO_ERROR);
        }
    };

    private UpdateFile.DownloadAsync.DownloadListener listener =
            new UpdateFile.DownloadAsync.DownloadListener() {
        @Override
        public void onProgress(int progress) {
            mProgressDialog.setProgress(progress);
        }

        @Override
        public void onSuccess() {
            disProgress(true);
            UpdateFile.installApp(targetFile, getApplicationContext(), handler);
        }

        @Override
        public void onFail() {
            handler.sendEmptyMessage(UpdateConfig.DOWNLOAD_ERROR);
        }
    };

    /**
     * Type of dialog to show
     */
    public enum DialogType{
        MUST, CAN, ERROR
    }
}

Remember to add configuration to the list

        <service android:name=".UpdateService"/>

Verify that updates are required

public class UpdateCheck {

    /**
     * Is the currently connected network available
     * @param context
     * @return
     */
    public static boolean isNetWorkAvailable(Context context) {
        if (context != null) {
            ConnectivityManager connectivityManager = (ConnectivityManager)
                    context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (connectivityManager != null) {
                NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
                if (activeNetworkInfo.isConnected()){
                    return activeNetworkInfo.isAvailable();
                }else{
                    return false;
                }
            } else {
                return false;
            }
        }
        return false;
    }

    /**
     * Is the network connected
     * @param context
     * @return
     */
    public static boolean isNetWorkConnected(Context context){
        if (context!=null){
            ConnectivityManager connectivityManager = (ConnectivityManager)
                    context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (connectivityManager!=null){
                NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
                return activeNetworkInfo.isConnected();
            }else {
                return false;
            }
        }
        return false;
    }

    /**
     * Check version
     * @param client
     * @param url
     * @param version
     * @param packageName
     * @param result
     */
    public static void checkVersion(OkHttpClient client,
                                    String url,
                                    String version,
                                    String packageName,
                                    CheckVersionResult result){
        if (TextUtils.isEmpty(url)){
            result.fail(-1);
        }else {
            url = url + "version=V"+version+"&packAge="+"com.mieasy.maintanence"+"&osType=2";
            Log.i("EricLog", "url = \n"+url);
            Request.Builder request = new Request.Builder();
            request.url(url);
            client.newCall(request.get().build()).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    result.error(e);
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    if (response.isSuccessful()){
                        Gson gson = new Gson();
                        String s = response.body().string();
                        UpdateBean info = gson.fromJson(s, UpdateBean.class);
                        result.success(info);
                        if (!call.isCanceled()){
                            call.cancel();
                        }
                    }else {
                        result.fail(response.code());
                        if (!call.isCanceled()) {
                            call.cancel();
                        }
                    }
                }
            });
        }
    }

    interface CheckVersionResult{
    	//UpdateBean is the replacement of entity class
        void success(UpdateBean info);
        void fail(int code);
        void error(Throwable throwable);
    }
}

Some dialog boxes that will be used

public class UpdateDialog {

    /**
     * Force update dialog
     * @param context
     * @param msg
     * @param listener
     * @return
     */
    public static Dialog mustUpdate(Context context,
                              String msg,
                              DialogInterface.OnClickListener listener){
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("Version update");
        builder.setMessage(msg);
        builder.setCancelable(false);
        builder.setPositiveButton("To update", listener);
        return builder.create();
    }

    /**
     * Dialog box can be updated
     * @param context
     * @param msg
     * @param listener
     * @param cancel
     * @return
     */
    public static Dialog canUpdate(Context context,
                             String msg,
                             DialogInterface.OnClickListener listener,
                             DialogInterface.OnCancelListener cancel){
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("Version update");
        builder.setMessage(msg);
        builder.setCancelable(false);
        builder.setPositiveButton("To update", listener);
        builder.setNegativeButton("Not updated", listener);
        builder.setOnCancelListener(cancel);
        return builder.create();
    }

    /**
     * Version get exception dialog box
     * @param context
     * @param msg
     * @param listener
     * @return
     */
    public static Dialog errorDialog(Context context,
                               String msg,
                               DialogInterface.OnClickListener listener){
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("Version update");
        builder.setMessage(msg);
        builder.setCancelable(false);
        builder.setNegativeButton("Determine", listener);
        return builder.create();
    }

    /**
     * Update progress dialog
     * @param context
     * @return
     */
    public static ProgressDialog durationDialog(Context context){
        ProgressDialog dialog = new ProgressDialog(context);
        dialog.setTitle("Version update");
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setCancelable(false);
        return dialog;
    }
}

Download files to update

public class UpdateFile {

    public static class CheckFile extends AsyncTask<String, Integer, Void>{

        private File file;
        private String fileUrl;
        private Handler handler;

        public CheckFile(File file, String fileUrl, Handler handler){
            this.file = file;
            this.fileUrl = fileUrl;
            this.handler = handler;
        }

        @Override
        protected Void doInBackground(String... strings) {
            if (file.exists()){
                if (verifyFile(fileUrl,file)){
                    //If the document is complete
                    handler.sendEmptyMessage(FILE_EXIST_INTACT);
                }else {
                    //If the file is incomplete, delete the existing file first, and then download the file
                    if (!file.delete()) {
                        handler.sendEmptyMessage(FILE_DELETE_ERROR);
                        return null;
                    }
                    handler.sendEmptyMessage(FILE_NOT_INTACT);
                }
            }else {
                handler.sendEmptyMessage(FILE_NOT_EXIST);
            }
            return null;
        }
    }

    /**
     * Verify network files
     * @param mFile
     * @param fileUrl
     * @param handler
     */
    public static void checkFile(File mFile, String fileUrl, Handler handler) {
        Log.i("EricLog", "Verify File");
        if (mFile.exists()){
            if (verifyFile(fileUrl,mFile)){
                //If the document is complete
                handler.sendEmptyMessage(FILE_EXIST_INTACT);
            }else {
                //If the file is incomplete, delete the existing file first, and then download the file
                if (!mFile.delete()) {
                    handler.sendEmptyMessage(FILE_DELETE_ERROR);
                    return;
                }
                handler.sendEmptyMessage(FILE_NOT_INTACT);
            }
        }else {
            handler.sendEmptyMessage(FILE_NOT_EXIST);
        }
    }

    /**
     * Check whether the document is complete
     * @param urlLoadPath
     * @param file
     * @return
     */
    private static boolean verifyFile(String urlLoadPath, File file){
        long length = file.length();//Downloaded file length
        long realLength = getFileLength(urlLoadPath);//File length obtained by network
        Log.e("EricLog", "Download length:"+length+"\t\t File length:"+realLength);
        if (length != 0){
            if (realLength != 0 && (realLength == length)){
                return true;
            }
        }
        return false;
    }

    /**
     * Get the length of the file to download
     * @param url
     * @return
     */
    private static long getFileLength(String url) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .build();
        Response response;
        try {
            response = client.newCall(request).execute();
            if (response.isSuccessful()){
                long length = response.body().contentLength();
                response.body().close();
                return length;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

	//install
    public static void installApp(File file, Context context, Handler handler) {
        if (!file.exists()) {
            return;
        }
        // Jump to the new version of APP installation page
        try {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N){
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID+".fileProvider",file);
                intent.setDataAndType(uri, "application/vnd.android.package-archive");
            }else {
                intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
            }
            context.startActivity(intent);
        }catch (Throwable throwable){
            Log.e("EricLog", "Error = "+throwable.getMessage());
            handler.sendEmptyMessage(UpdateConfig.INSTALL_ERROR);
        }
    }

    /**
     * Asynchronous network file download and save
     */
    public static class DownloadAsync extends AsyncTask<String,Integer,Integer> {

        private static final int DOWNLOAD_SUCCESS = 1;
        private static final int DOWNLOAD_FAIL = 2;

        private DownloadListener listener;

        private int lastProgress;
        private File file;

        public DownloadAsync(File file, DownloadListener listener){
            this.file = file;
            this.listener = listener;
        }

        public interface DownloadListener{
            void onProgress(int progress);
            void onSuccess();
            void onFail();
        }

        @Override
        protected Integer doInBackground(String... strings) {
            long downloadLength = 0;
            InputStream inputStream = null;
            FileOutputStream fileOutputStream = null;
            long contentLength = getFileLength(strings[0]);
            try {
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder()
                        .url(strings[0])
                        .build();
                Response response = client.newCall(request).execute();
                if (response.code() == 200) {
                    inputStream = response.body().byteStream();
                    fileOutputStream = new FileOutputStream(file);
                    byte[] b = new byte[1024];
                    int total = 0;
                    int len;
                    while ((len = inputStream.read(b)) != -1) {
                        total += len;
                        fileOutputStream.write(b, 0, len);
                        int progress = (int) (((total + downloadLength) * 100) / contentLength);
                        publishProgress(progress);
                    }
                    response.body().close();
                    return DOWNLOAD_SUCCESS;
                }else {
                    return DOWNLOAD_FAIL;
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (fileOutputStream != null) {
                        fileOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return DOWNLOAD_FAIL;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            int progress = values[0];
            if (progress > lastProgress){
                listener.onProgress(progress);
                lastProgress = progress;
            }
        }

        @Override
        protected void onPostExecute(Integer integer) {
            switch (integer){
                case DOWNLOAD_SUCCESS:
                    listener.onSuccess();
                    break;
                case DOWNLOAD_FAIL:
                    listener.onFail();
                    break;
                default: break;
            }
        }
    }
}

Some notes on installation after downloading files
add permission

    <!--install files-->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

For Android N (7.0) and above

    <application
    	...
        <service android:name=".update.UpdateService"/>

		<!--At Android7.0The above is forbidden to be disclosed outside the application file://URI-->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.myapplication.fileProvider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
                <!--Above resource="xxx"Refers to a document,file_path File name-->
        </provider>

    </application>

Create a new (or Alt+Enter) xml folder in res
Just create a new resource in xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="install_eric"
        path="."/>
</paths>
<!--Above name Write casually  path write.It means that any directory can be explained by other blogs-->

Basically, there are some optimization operations, such as
Check whether it is data network before downloading update file
How to deal with errors in the download process

Published 14 original articles, won praise 1, visited 515
Private letter follow

Tags: Android network FileProvider xml

Posted on Wed, 05 Feb 2020 10:46:03 -0500 by jonybhi