首页
 

通知公告

Android下载apk并安装apk(用于软件版本升级用途)

来源:欧亿体育点击:时间:2024-01-23 21:04
软件版本更新是每个应用必不可少的功能,基本实现方案是请求服务器最新的版本号与本地的版本号对比,有新版本则下载apk并执行安装。请求服务器版本号与本地对比很容易,本文就不过多讲解,主要讲解下载apk到安装apk的内容。

一、所需权限


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

<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"/>
(1)读写外部存储的权限需要动态申请,详见:Android动态获取权限
(2)安装apk的权限从Android8.0开始需要每个应用独立开启
//跳转到开启apk安装权限开启的界面,让用户手动打开
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,Uri.parse("package:" +getPackageName()));
intentActivityResultLauncher.launch(intent);

二、代码实现

(1)注册provider

在AndroidManifest.xml中声明provider


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="你的包名">

    
    
    <application
        省略属性。。。>

        <activity
        省略属性。。。>

        
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="你的包名.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        provider>

    application>

manifest>

在res的xml目录增加filepaths.xml

filepaths.xml中配置path路径

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <path>
        <root-path name="files_apk"
            path="/"/>
    </path>
</paths>
(2)动态申请权限基础BaseActivity

这个类在另外一篇文章中讲解,主要为了方便动态获取权限。

package com.soface.versioncontroll;

import android.content.pm.PackageManager;
import android.os.Build;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.util.ArrayList;
import java.util.List;

public class BaseActivity extends AppCompatActivity {

    public static final int REQUEST_CONDE =0xFFFF;

    /**
     * 动态请求权限(入口)
     * @param permissionNameList 需要授权的权限名称
     */
    public void requestPermission(List<String> permissionNameList){

        // TODO: 2023/2/22 第一步:排除(已经获得过授权的权限)=============================================================
        List<String> UnauthorizedPermissionNameList = new ArrayList<>();//用于存放未获得授权的权限
        for (String permission : permissionNameList){
            //检查每个权限是否已经获得授权
            int checkResult=ContextCompat.checkSelfPermission(this,permission);
            if (checkResult==PackageManager.PERMISSION_GRANTED){
                //已获得过授权,直接抛出结果true
                throwPermissionResults(permission,true);
            }else if (checkResult==PackageManager.PERMISSION_DENIED) {
                //未获得授权,把未获得授权的权限加入到thisPermissionNames中,待下一步请求
                UnauthorizedPermissionNameList.add(permission);
            }else {
                //按道理,这里永远不会发生,
                //因为checkSelfPermission方法说得很清楚,只会返回PERMISSION_GRANTED或者PERMISSION_DENIED
                //但是为了严谨,以防万一,还是给它抛出结果false
                throwPermissionResults("Unknown_result",false);
            }
        }
        if (UnauthorizedPermissionNameList.size()==0)return;//表示:全部已经拥有全选,不用往下执行

        // TODO: 2023/2/22 第二步:开始申请权限==========================================================================
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //由于请求权限的参数必须是String[],所以List转到String[]中
            String[] UnauthorizedPermissionNames=new String[UnauthorizedPermissionNameList.size()];
            for (int k=0;k<UnauthorizedPermissionNameList.size();k++){
                UnauthorizedPermissionNames[k]=UnauthorizedPermissionNameList.get(k);
            }
            //请求权限
            ActivityCompat.requestPermissions(this, UnauthorizedPermissionNames, REQUEST_CONDE);
        }else {
            //低版本的Android不需要动态获取权限,这里直接抛出结果true
            throwPermissionResults("Below_VERSION_M",true);
        }
    }

    /**
     * 抛出授权结果(出口)
     * @param permissionName 权限名称
     * @param isSuccess 该权限是否获得授权(true:获得授权  false:未获得授权)
     */
    public void throwPermissionResults(String permissionName, boolean isSuccess){
        // TODO: 2023/2/22 这里如果isSuccess=false,可以自定义一个弹窗,让用户选择
        //  到底是要直接退出应用,还是去设置中开启权限,本文主要是总结动态获取权限,所以弹窗笔者就不写了
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        //判断我们的请求码,避免别的事件调用onRequestPermissionsResult,导致我们拿到本不该属于我们的数据
        if (requestCode==REQUEST_CONDE){
            // 如果请求被取消,则结果数组为空。
            if (grantResults.length > 0) {
                //循环一个一个地去判断结果
                for (int k=0;k<permissions.length;k++){

                    if (grantResults[k] == PackageManager.PERMISSION_GRANTED){
                        // 权限请求成功,抛出结果true
                        throwPermissionResults(permissions[k],true);
                    }

                    if (grantResults[k] == PackageManager.PERMISSION_DENIED){
                        // 权限请求失败,抛出结果false
                        throwPermissionResults(permissions[k],false);
                    }
                }
            } else {
                //没有任何授权结果,直接抛出结果false
                throwPermissionResults("Unknown_result",false);
            }
        }
    }

}
(3)判断需不需要升级最新软件的MainActivity
package com.soface.versioncontroll;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends BaseActivity{


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //动态请求权限
        List<String> perList=new ArrayList<>();
        perList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        perList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
        perList.add(Manifest.permission.INTERNET);
        requestPermission(perList);

        //初始化结果返回接听
        initActivityResult();

        Button permission=(Button) findViewById(R.id.permission);
        permission.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            	//当判断需要升级最新软件,则调用这个方法,这里为了方便测试,放在点击事件中
                openSetting();
            }
        });
    }

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

    @Override
    public void throwPermissionResults(String permissionName, boolean isSuccess) {
        super.throwPermissionResults(permissionName, isSuccess);
        //拿到相应的权限,以及授权结果
        switch (permissionName){
            case Manifest.permission.WRITE_EXTERNAL_STORAGE:
                Log.d("fxHou","WRITE_EXTERNAL_STORAGE授权结果:"+isSuccess);
                break;
            case Manifest.permission.READ_EXTERNAL_STORAGE:
                Log.d("fxHou","READ_EXTERNAL_STORAGE授权结果:"+isSuccess);
                break;
            default:
                break;
        }
    }

    public void openSetting() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //Android 8.0以上
            if(!getPackageManager().canRequestPackageInstalls()){
                //权限没有打开,跳转界面,提示用户去手动打开
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,Uri.parse("package:" +getPackageName()));
                intentActivityResultLauncher.launch(intent);
            }else {
                //已经拥有权限,直接执行下载apk操作
                start();
            }
        }else {
            //开始下载安装
            start();
        }
    }
    private ActivityResultLauncher<Intent> intentActivityResultLauncher;
    private void initActivityResult() {
        intentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == AppCompatActivity.RESULT_OK) {
                //开始下载安装
                start();
            }
        });
    }


    VersionControl versionControl;
    String downloadUrl="http://www.soface.top:8080/source/Public/ApkVersionControl/chart.apk";
    String titleStr="麦麦商家版V1.1.2";
    String contentStr="正在下载中,请耐心等待";
    //开始执行版本更新操作
    public void start(){
        //初始化版本控制
        versionControl=new VersionControl();
        versionControl.download(this,downloadUrl,titleStr,contentStr);
        versionControl.registerReceiver(this);
    }
    //停止执行版本更新操作
    public void stop(){
        //初始化版本控制
        versionControl.unRegisterReceiver(MainActivity.this);
        versionControl=null;
    }
}
(4)下载apk和安装apk的实现类
package com.soface.versioncontroll;

import static android.content.Context.DOWNLOAD_SERVICE;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import java.io.File;

public class VersionControl {

    //第一步: 下载APK
    private long downloadId=-1;
    private DownloadManager downloadManager;
    public void download(Context context,String url,String titleStr,String contentStr) {
        //创建下载任务
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        //在通知栏中显示,默认就是显示的
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        request.setTitle(titleStr);
        request.setDescription(contentStr);
        //设置下载的路径
        File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "chart.apk");
        request.setDestinationUri(Uri.fromFile(file));
        file.getAbsolutePath();
        //获取DownloadManager
        downloadManager = (DownloadManager)context.getSystemService(DOWNLOAD_SERVICE);
        //将下载请求放入队列
        downloadId = downloadManager.enqueue(request);
    }

    //第二步: 监听下载结果
    private BroadcastReceiver broadcastReceiver;
    public void registerReceiver(Context context) {
        // 注册广播监听系统的下载完成事件。
        IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                long thisDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                if (thisDownloadId!=-1 && downloadId!=-1){
                    if (thisDownloadId == downloadId) {
                        //下载完成,检查下载状态
                        checkStatus(context);
                    }
                }
            }
        };
        context.registerReceiver(broadcastReceiver, intentFilter);
    }
    public void unRegisterReceiver(Context context){
        if (broadcastReceiver!=null) {
            context.unregisterReceiver(broadcastReceiver);
        }
    }

    //第三部: 检查下载状态,是否下载成功
    @SuppressLint("Range")
    private void checkStatus(Context context) {

        DownloadManager.Query query = new DownloadManager.Query();
        // 执行查询, 返回一个 Cursor (相当于查询数据库)
        Cursor cursor = downloadManager.query(query);
        if (!cursor.moveToFirst()) {
            cursor.close();
        }
        int id = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
        //通过下载的id查找
        query.setFilterById(id);

        // 获取下载好的 apk 路径
        String localFilename = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
        } else {
            localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
        }

        if (cursor.moveToFirst()) {
            int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
            switch (status) {
                case DownloadManager.STATUS_PAUSED:
                    //下载暂停
                    Log.d("fxHou","下载暂停");
                    break;
                case DownloadManager.STATUS_PENDING:
                    //下载延迟
                    Log.d("fxHou","下载延迟");
                    break;
                case DownloadManager.STATUS_RUNNING:
                    //正在下载
                    Log.d("fxHou","正在下载");
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    //下载完成安装APK
                    installApk(context,localFilename);
                    cursor.close();
                    break;
                case DownloadManager.STATUS_FAILED:
                    //下载失败
                    Log.d("fxHou","下载失败");
                    cursor.close();
                    break;
                default:
                    break;
            }
        }
    }

    //第四部: 安装apk
    private void installApk(Context context,String path) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        File file = new File(Uri.parse(path).getPath());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri uri = FileProvider.getUriForFile(context, "你的包名.fileprovider", file);
            intent.setDataAndType(uri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

}