栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

前言

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

下载

参考链接:
Fetch

环境配置:

build.gradle中引入:

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

AndroidManifest.xml中配置:




// 以上三种就OK

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);
 }
监听状态

监听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 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);
     }
 };
全部代码
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;


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);
    }

    
    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 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);
        }
    };

    
    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() {
            @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、理论上先检测系统上是否装有该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;


public class ApkInstallUtil {
    private static final String TAG = "ApkInstallUtil";
    public static final String ACTION_APK_INSTALL_SUCCESS = "action.apk_install_success";

    
    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;
    }

    
    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;
    }

    
    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;
        }
    }

    
    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;
    }

    
    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;
    }

    
    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();
            }
        }
    }

}

安装成功状态通知

当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;


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);
        });
    }

    
    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 二次安装?
                }
            }
        }
    }
}

其他 Dialog UI



    

    

    

    

        
    

    

    



Dialog bg


    
        
            
            
        
    

Progress bar


    
        
            
            
        
    

    
        
            
                
                
            
        
    
    
        
            
                
                
            
        
    


线程池
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;


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);
        }
    }
}

小结

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1006490.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号