• Apk的下载(Fetch)和静默安装


    前言

    在做需求开发时,少不了需要从服务器上直接下载东西,比如图片、视频、apk等。下载后的东西,还需要做起一些其他操作。
    对于下载,可以采用WorkManager,也可以采用现有的包,比如Fetch。下载后的apk,针对Android 9.0版本,采用静默安装。
    注意:系统应用才有权限静默安装

    下载

    参考链接:
    Fetch

    环境配置:

    build.gradle中引入:

     implementation 'androidx.tonyodev.fetch2:xfetch2:3.1.6'
    
    • 1

    AndroidManifest.xml中配置:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    // 以上三种就OK
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Fetch使用

    Fetch初始化

    public void initFetch(){
    	final FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(mContext)
    	        .setDownloadConcurrentLimit(4) // 并行下载数量
    	        .setAutoRetryMaxAttempts(3)    // 失败时,重试3次
    	        .setNamespace(FETCH_NAMESPACE)
                    .setProgressReportingInterval(PROGRESS_REPORTING_INTERVAL)
                    .build();
        mFetch = Fetch.Impl.getInstance(fetchConfiguration);
         mFetch.addListener(fetchListener);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    监听状态

    监听Fetch的状态,例如下载时使用进度条查看百分比

    /**
      * Fetch状态监听
      */
     private final FetchListener fetchListener = new AbstractFetchListener() {
         @Override
         public void onAdded(@NonNull Download download) {
             super.onAdded(download);
             Log.d(TAG,"fetchListener: onAdded : file="+download.getFile());
         }
    
         @Override
         public void onCancelled(@NonNull Download download) {
             super.onCancelled(download);
             Log.d(TAG,"fetchListener: onCancelled : file="+download.getFile());
         }
    
         @Override
         public void onCompleted(@NonNull Download download) {
             if (listener!=null){
                 listener.onCompleted(download.getFile());
                 Log.d(TAG,"fetchListener file="+download.getFile());
             }
             super.onCompleted(download);
         }
    
         @Override
         public void onDeleted(@NonNull Download download) {
             super.onDeleted(download);
             Log.d(TAG,"fetchListener: onDeleted : file="+download.getFile());
         }
    
         @Override
         public void onDownloadBlockUpdated(@NonNull Download download, @NonNull DownloadBlock downloadBlock, int totalBlocks) {
             super.onDownloadBlockUpdated(download, downloadBlock, totalBlocks);
             Log.d(TAG,"fetchListener: onDownloadBlockUpdated : file="+download.getFile());
         }
    
         @Override
         public void onError(@NonNull Download download, @NonNull Error error, @Nullable Throwable throwable) {
             Log.e(TAG,"fetchListener: onError: msg="+error.toString()+" filePath="+download.getFile());
             super.onError(download, error, throwable);
         }
    
         @Override
         public void onPaused(@NonNull Download download) {
             super.onPaused(download);
             Log.d(TAG,"fetchListener: onPaused : file="+download.getFile());
         }
    
         @Override
         public void onProgress(@NonNull Download download, long etaInMilliSeconds, long downloadedBytesPerSecond) {
             long logTime = 0;
             if (SystemClock.uptimeMillis() - logTime > TimeUnit.SECONDS.toMillis(10)) {
                 Log.d(TAG, "downloading: "+download.getFile()+" :progress "+download.getProgress());
                 logTime = SystemClock.uptimeMillis();
             }
             ThreadPoolUtil.runMain(()->{
                 mDialogManager.updateViews(download);
             });
             super.onProgress(download, etaInMilliSeconds, downloadedBytesPerSecond);
             Log.d(TAG,"fetchListener: onProgress : file="+download.getFile());
         }
    
         @Override
         public void onQueued(@NonNull Download download, boolean waitingOnNetwork) {
             super.onQueued(download, waitingOnNetwork);
             Log.d(TAG,"fetchListener: onQueued : file="+download.getFile());
         }
    
         @Override
         public void onRemoved(@NonNull Download download) {
             super.onRemoved(download);
         }
    
         @Override
         public void onResumed(@NonNull Download download) {
             super.onResumed(download);
         }
    
         @Override
         public void onStarted(@NonNull Download download, @NonNull List<? extends DownloadBlock> downloadBlocks, int totalBlocks) {
             super.onStarted(download, downloadBlocks, totalBlocks);
         }
    
         @Override
         public void onWaitingNetwork(@NonNull Download download) {
             Log.d(TAG,"fetchListener onWaitingNetwork "+download.getFile());
             super.onWaitingNetwork(download);
         }
     };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    全部代码

    package com.ym.fetchdemo.util;
    
    import android.content.Context;
    import android.net.Uri;
    import android.os.Environment;
    import android.os.SystemClock;
    import android.util.Log;
    
    import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;
    
    import com.tonyodev.fetch2.AbstractFetchListener;
    import com.tonyodev.fetch2.Download;
    import com.tonyodev.fetch2.Error;
    import com.tonyodev.fetch2.Fetch;
    import com.tonyodev.fetch2.FetchConfiguration;
    import com.tonyodev.fetch2.FetchListener;
    import com.tonyodev.fetch2.Request;
    import com.tonyodev.fetch2core.DownloadBlock;
    import com.tonyodev.fetch2core.FetchObserver;
    import com.tonyodev.fetch2core.Reason;
    import com.ym.fetchdemo.DialogManager;
    
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    /**
     * Author:Yangmiao
     * Desc:
     * Time:2022/7/4 10:09
     */
    public class FetchData {
        private static final String TAG = "FetchData";
        private static final String FETCH_NAMESPACE = "FetchData";
        private static final long PROGRESS_REPORTING_INTERVAL = 100;
        private Context mContext;
        private Fetch mFetch;
        private Request mRequest;
    
        public DialogManager mDialogManager;
    
        private DownloadCompletedListener listener;
    
        public FetchData(Context context){
            this.mContext = context;
            mDialogManager = new DialogManager(mContext);
        }
    
        public void initFetch(){
            final FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(mContext)
                    .setDownloadConcurrentLimit(4) // 并行下载数量
                    .setAutoRetryMaxAttempts(3)    // 失败时,重试3次
                    .setNamespace(FETCH_NAMESPACE)
                    .setProgressReportingInterval(PROGRESS_REPORTING_INTERVAL)
                    .build();
            mFetch = Fetch.Impl.getInstance(fetchConfiguration);
            mFetch.addListener(fetchListener);
        }
    
        /**
         * Fetch状态监听
         */
        private final FetchListener fetchListener = new AbstractFetchListener() {
            @Override
            public void onAdded(@NonNull Download download) {
                super.onAdded(download);
                Log.d(TAG,"fetchListener: onAdded : file="+download.getFile());
            }
    
            @Override
            public void onCancelled(@NonNull Download download) {
                super.onCancelled(download);
                Log.d(TAG,"fetchListener: onCancelled : file="+download.getFile());
            }
    
            @Override
            public void onCompleted(@NonNull Download download) {
                if (listener!=null){
                    listener.onCompleted(download.getFile());
                    Log.d(TAG,"fetchListener file="+download.getFile());
                }
                super.onCompleted(download);
            }
    
            @Override
            public void onDeleted(@NonNull Download download) {
                super.onDeleted(download);
                Log.d(TAG,"fetchListener: onDeleted : file="+download.getFile());
            }
    
            @Override
            public void onDownloadBlockUpdated(@NonNull Download download, @NonNull DownloadBlock downloadBlock, int totalBlocks) {
                super.onDownloadBlockUpdated(download, downloadBlock, totalBlocks);
                Log.d(TAG,"fetchListener: onDownloadBlockUpdated : file="+download.getFile());
            }
    
            @Override
            public void onError(@NonNull Download download, @NonNull Error error, @Nullable Throwable throwable) {
                Log.e(TAG,"fetchListener: onError: msg="+error.toString()+" filePath="+download.getFile());
                super.onError(download, error, throwable);
            }
    
            @Override
            public void onPaused(@NonNull Download download) {
                super.onPaused(download);
                Log.d(TAG,"fetchListener: onPaused : file="+download.getFile());
            }
    
            @Override
            public void onProgress(@NonNull Download download, long etaInMilliSeconds, long downloadedBytesPerSecond) {
                long logTime = 0;
                if (SystemClock.uptimeMillis() - logTime > TimeUnit.SECONDS.toMillis(10)) {
                    Log.d(TAG, "downloading: "+download.getFile()+" :progress "+download.getProgress());
                    logTime = SystemClock.uptimeMillis();
                }
                ThreadPoolUtil.runMain(()->{
                    mDialogManager.updateViews(download);
                });
                super.onProgress(download, etaInMilliSeconds, downloadedBytesPerSecond);
                Log.d(TAG,"fetchListener: onProgress : file="+download.getFile());
            }
    
            @Override
            public void onQueued(@NonNull Download download, boolean waitingOnNetwork) {
                super.onQueued(download, waitingOnNetwork);
                Log.d(TAG,"fetchListener: onQueued : file="+download.getFile());
            }
    
            @Override
            public void onRemoved(@NonNull Download download) {
                super.onRemoved(download);
            }
    
            @Override
            public void onResumed(@NonNull Download download) {
                super.onResumed(download);
            }
    
            @Override
            public void onStarted(@NonNull Download download, @NonNull List<? extends DownloadBlock> downloadBlocks, int totalBlocks) {
                super.onStarted(download, downloadBlocks, totalBlocks);
            }
    
            @Override
            public void onWaitingNetwork(@NonNull Download download) {
                Log.d(TAG,"fetchListener onWaitingNetwork "+download.getFile());
                super.onWaitingNetwork(download);
            }
        };
    
        /**
         * 下载
         * @param tagUrl 下载链接
         * @param filePath 下载存放的路径
         */
        public void enqueueDownload(String tagUrl,String filePath){
            Log.d(TAG,"enqueueDownload tagUrl="+tagUrl+" filePath="+filePath);
            mRequest = new Request(tagUrl,filePath);
            mFetch.attachFetchObserversForDownload(mRequest.getId(), new FetchObserver<Download>() {
                @Override
                public void onChanged(Download download, @NonNull Reason reason) {
                    if (reason == Reason.DOWNLOAD_ADDED){
                        Log.d(TAG,"enqueueDownload Reason.DOWNLOAD_ADDED");
                    }
                }
            });
        }
    
        public void closeFetch(){
            mFetch.close();
            Log.d(TAG,"closeFetch");
        }
    
        @NonNull
        public static String getSaveDir(Context context) {
            return context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/fetch";
        }
    
        public static String getNameFromUrl(final String url){
            return Uri.parse(url).getLastPathSegment();
        }
    
        public void setDownloadCompletedListener(DownloadCompletedListener listener){
            this.listener = listener;
        }
    
        public interface DownloadCompletedListener{
            void onCompleted(String filePath);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191

    安装

    安装过程:
    1、理论上先检测系统上是否装有该app,如果已安装,安装的前提是已下载的apk的versionCode>系统当前apk。
    2、调用系统的PackageInstall安装,通过文件流传输apk,将apk数据写入到session中,最后才执行安装,并将安装结果广播。

    package com.ym.fetchdemo.util;
    
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageInstaller;
    import android.text.TextUtils;
    import android.util.Log;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    /**
     * Author:Yangmiao
     * Desc:
     * Time:2022/7/4 11:02
     */
    public class ApkInstallUtil {
        private static final String TAG = "ApkInstallUtil";
        public static final String ACTION_APK_INSTALL_SUCCESS = "action.apk_install_success";
    
        /**
         * 检测系统中是否安装了该apk
         * @param context
         * @param packageName App包名
         * @return
         */
        public static boolean isAppInstall(Context context,String packageName){
            if (TextUtils.isEmpty(packageName)){
                return false;
            }
            try {
                context.getPackageManager().getPackageInfo(packageName,0);
            }catch (Exception e){
                Log.e(TAG,"isAppInstall ",e);
                return false;
            }
            return true;
        }
    
        /**
         * 获取App的versionCode
         * @param context
         * @param packageName
         * @return
         */
        public static int getVersionCode(Context context,String packageName){
            try {
                PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName,0);
                if (packageInfo!=null){
                    Log.d(TAG,"getVersionCode versionCode="+packageInfo.versionCode);
                    return packageInfo.versionCode;
                }
            }catch (Exception e){
                Log.d(TAG,"getVersionCode ",e);
                return 0;
            }
            return 0;
        }
    
        /**
         * 删除文件
         * @param filePath
         * @return
         */
        public static boolean deleteFile(String filePath){
            if (TextUtils.isEmpty(filePath)){
                return false;
            }
            File file = new File(filePath);
            if (file.exists() && file.isFile()){
                if (file.delete()){
                    Log.d(TAG,"deleteFile filePath="+filePath+" delete success!");
                    return true;
                }else {
                    Log.d(TAG,"deleteFile filePath="+filePath+" delete failure!");
                    return false;
                }
            }else {
                Log.d(TAG,"deleteFile filePath="+filePath+" is not exists!");
                return false;
            }
        }
    
        /**
         * 安装apk
         * @param context
         * @param filePath
         * @return
         */
        public static boolean checkAndInstallApk(Context context, String filePath){
            Log.d(TAG, "start install" + filePath);
            boolean isSuccess = true;
            File apkFile = new File(filePath);
            if (!apkFile.exists()) {
                isSuccess = false;
            } else {
                PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
                PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                sessionParams.setSize(apkFile.length());
                int mSessionId = -1;
                try {
                    mSessionId = packageInstaller.createSession(sessionParams);
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                Log.d(TAG, "sessionId---->" + mSessionId);
                if (mSessionId != -1) {
                    boolean copySuccess = onTransfersApkFile(context, filePath, mSessionId);
                    Log.d(TAG, "copySuccess---->" + copySuccess);
                    if (copySuccess) {
                        installAPP(context, mSessionId);
                    } else {
                        isSuccess = false;
                    }
                }
            }
            return isSuccess;
        }
    
        /**
         * 文件流传输apk,向session内传输数据
         * @param context
         * @param apkFilePath
         * @param mSessionId
         * @return
         */
        public static boolean onTransfersApkFile(Context context, String apkFilePath, int mSessionId) {
            InputStream in = null;
            OutputStream out = null;
            PackageInstaller.Session session = null;
            boolean success = false;
            try {
                File apkFile = new File(apkFilePath);
                session = context.getPackageManager().getPackageInstaller().openSession(mSessionId);
                out = session.openWrite("base.apk", 0, apkFile.length());
                in = new FileInputStream(apkFile);
                int total = 0, c;
                byte[] buffer = new byte[1024 * 1024];
                while ((c = in.read(buffer)) != -1) {
                    total += c;
                    out.write(buffer, 0, c);
                }
                session.fsync(out);
                //Log.d(TAG, "streamed " + total + " bytes");
                success = true;
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != session) {
                    session.close();
                }
                try {
                    if (null != out) {
                        out.close();
                    }
                    if (null != in) {
                        in.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return success;
        }
    
        /**
         * 执行安装并接受通知结果
         * @param context
         * @param mSessionId
         */
        public static void installAPP(Context context, int mSessionId) {
            PackageInstaller.Session session = null;
            try {
                session = context.getPackageManager().getPackageInstaller().openSession(mSessionId);
                Intent intent = new Intent();
                intent.setAction(ACTION_APK_INSTALL_SUCCESS);
                PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
                        1, intent,
                        PendingIntent.FLAG_UPDATE_CURRENT);
                Log.d(TAG, "installAPP  installing...");
                session.commit(pendingIntent.getIntentSender());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != session) {
                    session.close();
                }
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199

    安装成功状态通知

    当apk安装成功后,广播通知;
    安装失败,也可广播通知,然后执行二次安装。

    package com.ym.fetchdemo;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.content.pm.PackageInstaller;
    import android.util.Log;
    
    import com.ym.fetchdemo.util.ApkInstallUtil;
    import com.ym.fetchdemo.util.FetchData;
    import com.ym.fetchdemo.util.ThreadPoolUtil;
    
    /**
     * Author:Yangmiao
     * Desc:
     * Time:2022/7/4 11:36
     */
    public class ApkInstallManager implements FetchData.DownloadCompletedListener{
        private static final String TAG = "ApkInstallManager";
    
        private static volatile ApkInstallManager sInstance;
        private Context mContext;
        private InstallResultReceiver mInstallResultReceiver;
        private FetchData mFetchData;
    
        private String apkFilePath;
    
        public static ApkInstallManager getInstance(Context context){
            if (sInstance == null){
                synchronized (ApkInstallManager.class){
                    if (sInstance == null){
                        sInstance = new ApkInstallManager();
                        sInstance.mContext = context;
                        sInstance.mFetchData = new FetchData(context);
                    }
                }
            }
            return sInstance;
        }
    
        private ApkInstallManager(){
    
        }
    
        public void registerReceiver(){
            Log.d(TAG,"registerReceiver...");
            if (mInstallResultReceiver != null){
                mContext.unregisterReceiver(mInstallResultReceiver);
            }
            IntentFilter installIntentFilter = new IntentFilter(ApkInstallUtil.ACTION_APK_INSTALL_SUCCESS);
            mInstallResultReceiver = new InstallResultReceiver();
            mContext.registerReceiver(mInstallResultReceiver,installIntentFilter);
            mFetchData.initFetch();
            mFetchData.setDownloadCompletedListener(this);
    
            ThreadPoolUtil.runMain(()->{
                mFetchData.mDialogManager.showDownloadDialog();
            });
    
            // 下载apk
            ThreadPoolUtil.run(()->{
                String apkUrl = "https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk";
                String filePath = FetchData.getSaveDir(mContext)+"/app/"+FetchData.getNameFromUrl(apkUrl);
                mFetchData.enqueueDownload(apkUrl,filePath);
            });
        }
    
        @Override
        public void onCompleted(String filePath) {
            apkFilePath = filePath;
            ThreadPoolUtil.runMain(()->{
                mFetchData.mDialogManager.showInstallDialog();
            });
            ThreadPoolUtil.run(()->{
                ApkInstallUtil.checkAndInstallApk(mContext,filePath);
            });
        }
    
        /**
         * 安装结果广播receiver
         */
        private class InstallResultReceiver extends BroadcastReceiver {
            private final String TAG = "InstallResultReceiver";
            private String path;
            private int versionCode;
    
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent != null){
                    String action = intent.getAction();
                    Log.d(TAG,"Apk install broadcast action: "+action);
                    final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
                            PackageInstaller.STATUS_FAILURE);
                    if (status == PackageInstaller.STATUS_SUCCESS){
                        Log.d(TAG,"Apk install success! path: "+path);
                        mFetchData.mDialogManager.dismissDialog();
                        Log.d(TAG,"dismissDialog...");
                        mFetchData.closeFetch();
                        // 删除apk文件
                        ThreadPoolUtil.run(()->{
                            ApkInstallUtil.deleteFile(apkFilePath);
                        });
    
                    }else {
                        String msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
                        Log.e(TAG, "Apk install failure! status_massage: " + msg);
                        // TODO 二次安装?
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    其他

    Dialog UI

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="325dp"
        android:layout_height="300dp"
        android:background="@drawable/dialog_bg"
        android:orientation="vertical">
    
        <ImageView
            android:id="@+id/app_video_image"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_centerHorizontal="true"
            android:src="@mipmap/ic_launcher"
            android:layout_marginTop="40dp"/>
    
        <TextView
            android:id="@+id/app_video_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/app_video_image"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="20dp"
            android:text="Launcher"
            android:textColor="@color/black"
            android:textSize="24sp"
            android:textStyle="bold"/>
    
        <ProgressBar
            android:id="@+id/app_download_progress_bar"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:layout_centerHorizontal="true"
            android:layout_marginStart="15dp"
            android:layout_marginEnd="15dp"
            android:layout_marginTop="20dp"
            android:layout_below="@+id/app_video_name"
            android:max="100"
            android:progress="10"
            android:progressDrawable="@drawable/progress_bar"
            android:visibility="invisible"/>
    
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/app_download_progress_bar"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="20dp">
    
            <TextView
                android:id="@+id/app_download_percentage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=""
                android:textSize="18sp"
                android:textColor="@color/black"
                android:layout_marginStart="10dp"
                android:gravity="center_vertical"
                android:visibility="invisible"/>
        </LinearLayout>
    
        <ProgressBar
            android:id="@+id/app_install_progress_bar"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:layout_below="@+id/app_video_name"
            android:layout_centerHorizontal="true"
            android:layout_marginStart="15dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="15dp"
            android:max="100"
            android:progress="5"
            android:progressDrawable="@drawable/progress_bar"
            android:visibility="invisible"/>
    
        <TextView
            android:id="@+id/app_install_desc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="安装进行中..."
            android:textColor="@color/black"
            android:textSize="18sp"
            android:layout_below="@+id/app_install_progress_bar"
            android:layout_centerHorizontal="true"
            android:visibility="invisible"/>
    
    
    </RelativeLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    Dialog bg

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item>
            <shape android:shape="rectangle">
                <solid android:color="#FFFFFF" />
                <corners android:radius="15dp" />
            </shape>
        </item>
    </selector>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Progress bar

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@android:id/background">
            <shape>
                <corners android:radius="5dip" />
                <gradient
                    android:startColor="#FFFFFF"
                    android:centerColor="#FFFFFF"
                    android:centerY="0.75"
                    android:endColor="#FFFFFF"
                    android:angle="270"
                    />
            </shape>
        </item>
    
        <item android:id="@android:id/secondaryProgress">
            <clip>
                <shape>
                    <corners android:radius="5dip" />
                    <gradient
                        android:startColor="#80ffb600"
                        android:centerColor="#80ffb600"
                        android:centerY="0.75"
                        android:endColor="#80ffb600"
                        android:angle="270"
                        />
                </shape>
            </clip>
        </item>
        <item
            android:id="@android:id/progress"
            >
            <clip>
                <shape>
                    <corners
                        android:radius="5dip" />
                    <gradient
                        android:startColor="#2196f3"
                        android:endColor="#2196f3"
                        android:angle="270" />
                </shape>
            </clip>
        </item>
    
    </layer-list>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    线程池

    package com.ym.fetchdemo.util;
    
    import android.os.Handler;
    import android.os.Looper;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * Author:Yangmiao
     * Desc:
     * Time:2022/7/4 11:39
     */
    public class ThreadPoolUtil {
        private static final int CORE_POOL_SIZE = 1;
        private static final int MAX_POOL_SIZE = 10;
        private static final long KEEP_ALIVE_TIME = 30;
    
        private static final Handler mHandler = new Handler(Looper.getMainLooper());
    
        private static ThreadPoolExecutor sDiskExecutor = new ThreadPoolExecutor(
                    CORE_POOL_SIZE,
                    MAX_POOL_SIZE,
                    KEEP_ALIVE_TIME,
                    TimeUnit.SECONDS,
                    new LinkedBlockingDeque<>());
    
        public static void run(Runnable runnable){
            sDiskExecutor.execute(runnable);
        }
    
        public static void runMain(Runnable runnable){
            if (runnable!= null){
                mHandler.post(runnable);
            }
        }
    
        public static void cancelMain(Runnable runnable){
            if (runnable!=null){
                mHandler.removeCallbacks(runnable);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    小结

    文件的下载封装成一个类或者包,可以重复使用,同时处理一些边界case。
    监听下载的状态,下载成功、失败、正在下载、网络状态监听、恢复下载等,并可视化UI。

  • 相关阅读:
    2023下半年创业风口项目:实景自动无人直播!揭秘3大好处!
    MySQL 2 环境搭建(MySQL5.7.43和8.0.34的下载;8.0.34的安装、配置教程 )
    雷达编程实战之幅度与相位标定
    新测试人和转行测试的同学看过来 你需要知道的这几点
    打工人一定要学会找资源~
    windows10默认浏览器总是自动更改为Edge浏览器
    SpringBoot集成ElasticSearch(ES)
    slf4j简介说明
    最新版手机软件App下载排行网站源码/App应用商店源码
    Unity3D PRO 3D游戏制作系列教程第三课:认识菜单一
  • 原文地址:https://blog.csdn.net/baidu_33256174/article/details/125603988