Android应用版本更新,断点下载并安装

本文介绍如何在Android应用中实现版本更新功能,包括断点下载、权限申请、封装外层Bean、定义回调接口以及使用网络请求工具类进行文件下载,最终在Activity中调用相关功能进行无缝安装。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.依赖:

//《Okhttp网络请求依赖》
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
//《Gson解析依赖》
implementation 'com.google.code.gson:gson:2.8.2'
//《Butterknife依赖(黄油刀)》
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
2.权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
    tools:ignore="ProtectedPermissions" /><!--是否挂载-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

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

3.封装的外层Bean

package com.example.dell.versionupdatedemo05.bean;

public class MessageBean<T>{
    private Boolean Success;
    private String msg;
    private T data;

    public Boolean getSuccess() {
        return Success;
    }

    public void setSuccess(Boolean success) {
        Success = success;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
4.封装的内层Bean

package com.example.dell.versionupdatedemo05.bean;

public class VersionInfo {
    private int last_must_update;//需强制更新的版本
    private int last_version;//需更新的版本
    private String md5;//Md5码
    private String url;//下载地址

    public int getLast_must_update() {
        return last_must_update;
    }

    public void setLast_must_update(int last_must_update) {
        this.last_must_update = last_must_update;
    }

    public int getLast_version() {
        return last_version;
    }

    public void setLast_version(int last_version) {
        this.last_version = last_version;
    }

    public String getMd5() {
        return md5;
    }

    public void setMd5(String md5) {
        this.md5 = md5;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}
 
 

5.定义成功与失败的接口

package com.example.dell.versionupdatedemo05;

//定义成功与失败的方法
public interface NetCallBack {
    void Success(Object o);
    void Error(Throwable t);
}

6.网络请求的工具类

package com.example.dell.versionupdatedemo05.utils;

import android.os.Handler;
import com.example.dell.versionupdatedemo05.NetCallBack;
import com.google.gson.Gson;
import java.io.IOException;
import java.lang.reflect.Type;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class HttpUtils {
    //1.创建一个私有的静态的单列模式
    private static volatile HttpUtils instance;
    //13.创建一个公有的Handler
    public Handler handler = new Handler() {};
    private final OkHttpClient client;
    //2.创建一个私有的构造方法
    private HttpUtils(){
        //9.创建OkHttpClient
        client = new OkHttpClient();
    }
    //3.提供一个公有的静态方法
    public static HttpUtils getInstance(){
        //4.判空
        if(instance==null){
            //5.添加同步锁
            synchronized (HttpUtils.class){
                if(null==instance){
                    //6.双向判空后 进行创建
                    instance = new HttpUtils();
                }
            }
        }
        //7.返回instance
        return instance;
    }
    //8.创建一个方法 进行请求数据
    public void getData(String url, final Type type, final NetCallBack netCallBack){
        //10.创建Request对象
        final Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        //11.创建Call对象
        Call call = client.newCall(request);
        //12.进行异步请求
        call.enqueue(new Callback() {
            //失败的方法
            @Override
            public void onFailure(Call call,final IOException e) {
                //19.将失败的信息进行返回
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        netCallBack.Error(new Throwable(e));
                    }
                });
            }
            //成功的方法
            @Override
            public void onResponse(final Call call, Response response) throws IOException {
                //14.创建NetCallBack的接口
                // 15.解析数据
                String data = response.body().string();
                //16.使用Gson进行解析
                Gson gson = new Gson();
              final  Object o = gson.fromJson(data, type);
                //17.使用handler发送消息
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //18.将传给的信息进行返回
                        netCallBack.Success(o);
                    }
                });
            }
        });
    }
}
7.获取应用版本的工具类

package com.example.dell.versionupdatedemo05.utils;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

public class VersionUtils {
    //创建方法 得到本地版本
    public static int getVersionCode(Context context){
        //拿到包的管理器
        PackageManager packageManager = context.getPackageManager();
        PackageInfo packageInfo=null;
        try {
            //通过包名拿到版本号
            packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        //返回版本号
        return packageInfo.versionCode;
    }
}
8.MD5判断的工具类

package com.example.dell.versionupdatedemo05.utils;

import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.security.MessageDigest;

public class FileMd5Utils {
    public static String getFileMD5(File file) {
        if (!file.isFile()) {
            return null;
        }
        MessageDigest digest = null;
        FileInputStream in = null;
        byte buffer[] = new byte[1024];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }
}

9.Activity

package com.example.dell.versionupdatedemo05;

import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.example.dell.versionupdatedemo05.bean.MessageBean;
import com.example.dell.versionupdatedemo05.bean.VersionInfo;
import com.example.dell.versionupdatedemo05.utils.FileMd5Utils;
import com.example.dell.versionupdatedemo05.utils.HttpUtils;
import com.example.dell.versionupdatedemo05.utils.VersionUtils;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Type;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.mian_show)
    TextView mian_Show;
    @BindView(R.id.mian_btn)
    Button mian_Btn;
    private Boolean isMust = false;//定义状态默认为false
    private File file;//初始化地址
    private ProgressDialog dialog;//初始化下载进度条
    private static final int INTENT_APK = 0X123;//创建十六进制数
    private SharedPreferences sp;//数据存储
    private long fileLength = 0;//初始化数据总汉长度为0
    private Boolean isSaved = false;//定义标识 是否已经存储过长度

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        //初始化SharedPreferences
        sp = getSharedPreferences("info",MODE_PRIVATE);
        fileLength = sp.getLong("Length",0);
        isSaved = sp.getBoolean("is_saved",false);//初始化没有存储

        //判断外置存储是否挂载
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            //若挂载 则创建存放位置(SDK中)
            File externalStorageDirectory = Environment.getExternalStorageDirectory();
            String path = externalStorageDirectory.getAbsolutePath() + File.separator + "new.apk";
            Log.e("+++++存入路径", "onCreate: "+path );
            file = new File(path);
            //判断文件是否存在 不存在则创建
            if(!file.exists()){
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //创建下载进度条
        dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//进度条样式为水平
        dialog.setMax(100);//设置最大值为100
        dialog.setTitle("下载");
    }

    //点击时 进行版本检查
    @OnClick(R.id.mian_btn)
    public void onViewClicked() {
        //创建检测版本有无更新的方法
        CheckVersion();
    }

    //定义检测版本有无更新的方法
    private void CheckVersion() {
        Type type = new TypeToken<MessageBean<VersionInfo>>() {
        }.getType();
        HttpUtils.getInstance().getData("http://www.xieast.com/api/checkversion.php", type, new NetCallBack() {
            //成功的方法
            @Override
            public void Success(Object o) {
                //强转成我们所需的数据集合
                MessageBean<VersionInfo> messageBean = (MessageBean<VersionInfo>) o;
                //Log.e("+++++", "Success: "+messageBean.getMsg());
                VersionInfo info = messageBean.getData();
                //创建判断当前版本是否需要更新的方法
                isNeedUpdate(info);
            }
            //失败的方法
            @Override
            public void Error(Throwable t) {
                Log.e("+++++", "Error: " + t.getMessage());
            }
        });
    }

    //定义判断当前版本是否需要更新的方法
    private void isNeedUpdate(final VersionInfo info) {
        //拿到当前的版本号
        int currentversionCode = VersionUtils.getVersionCode(this);
        //判断当前版本号是否小于网络请求的需更新的版本号
        if (currentversionCode < info.getLast_version()) {
            //创建弹出对话框
            AlertDialog.Builder builder = new AlertDialog.Builder(this)
                    .setTitle("版本更新")
                    .setMessage("检查到新的版本");
            //需要更新 判断当前的版本是否小于等于网络请求的需强制更新的版本号
            if (currentversionCode < info.getLast_must_update()) {
                //需强制更新 将状态值变成false
                isMust = false;
                builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //创建更新APK文件的方法
                        downLoadApk(info.getUrl(),info.getMd5());
                        //设置监听事件
                    }
                }).setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        // 若不想更新 则点击模拟器返回按钮 关闭页面
                        finish();
                    }
                });
            } else {
                //可更新  不更新则将状态值变成false
                isMust = true;
                builder.setPositiveButton("更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //创建更新APK文件的方法
                        downLoadApk(info.getUrl(),info.getMd5());
                    }
                    //也可不更新
                }).setNegativeButton("取消", null);
            }
            //显示弹出框
            AlertDialog dialog = builder.create();
            //设置强制更新时 弹出框点外部不会消失
            //isMust为true 失去焦点不隐藏
            dialog.setCanceledOnTouchOutside(isMust);//设置弹出框失去焦点是否隐藏,即点击屏蔽其它地方是否隐藏
            dialog.show();
        } else {
            // 吐司提示
            Toast.makeText(this, "当前版本已经是最新版本了!", Toast.LENGTH_SHORT).show();
        }
    }

    //更新下载APK文件的方法
    private void downLoadApk(String url, final String md5) {
        Log.e("+++++", "downLoadApk: 开始下载" );
        String range = "";//定义变量
        //进行判断
        if(isSaved){
            //若存储过文件总长度 则直接将总长度赋给range即可
            range = "bytes="+file.length()+"-"+fileLength;
        }else{
            //若没有存储过 则直接指定range为0
            range = "bytes="+file.length()+"-";
        }

        //使用原生OkHttpClient进行请求
        OkHttpClient client = new OkHttpClient();
        final Request request = new Request.Builder()
                .url(url)
                .addHeader("range",range)
                .get()
                .build();
        Call call = client.newCall(request);
        //下载是显示进度条
        dialog.show();
        call.enqueue(new Callback() {
            @Override //失败
            public void onFailure(Call call, IOException e) {
                dialog.dismiss();//失败时,进度条不显示
                Log.e("+++++", "onFailure:下载失败 ");
            }

            @Override //成功
            public void onResponse(Call call, Response response) throws IOException {
                long contentLength =0;
                if(fileLength==0){
                    //得到数据的总长度
                    contentLength = response.body().contentLength();
                    //并存入数据存储中
                    sp.edit().putLong("Length",contentLength).commit();
                }else{
                    contentLength =fileLength;
                }

                Log.e("++++文件大小", "onResponse: "+contentLength);
                int length = 0;
                long sum = file.length() ;//定义一个总长度
                //创建随机存储的类 rw:可读可写模式
                RandomAccessFile raf = new RandomAccessFile(file,"rw");
                //移动到目前已经下载的文件大小的位置
                raf.seek(sum);
                //拿到流文件  InputStream输入流:往内存写数据
                InputStream inputStream = response.body().byteStream();
                //按照2048个字节进行写入
                byte[] bytes = new byte[2048];
                //循环写入
                while ((length = inputStream.read(bytes,0,bytes.length))!=-1){
                    sum+=length;//每次循环读取的数据赋给sum
                    raf.write(bytes,0,length);
                    raf.seek(sum);
                     //总长度*100/文件长度 = 进度
                    int progress = (int) (sum * 100 / contentLength);
                    //子线程不能更新UI,但是dialog内部已经回调到主线程了
                    dialog.setProgress(progress);//将进度赋给进度条
                    if(progress>99){
                        //下载完成 安装APK  并关闭dialog
                        dialog.dismiss();
                        //创建校验MD5值的方法
                        checkAPk(file,md5);
                        break;//循环完毕 跳出循环
                    }
                }
                //关流
                inputStream.close();
            }
        });
    }
    //创建校验MD5的方法
    private void checkAPk(File file, String md5) {
        //校验成功后再进行安装
        String fileMD5 = FileMd5Utils.getFileMD5(file);
        Log.e("+++++", "checkAPk:源文件的MD5值 "+fileMD5 );
        Log.e("+++++", "checkAPk: 下载的MD5值"+md5 );
        //toLowerCase:全部转为小写  equalsIgnoreCase:忽略大小写
        if(fileMD5.equalsIgnoreCase(md5)) {
            installAPK(file);//创建安装APK方法
        }else{
            Log.e("++++", "checkAPk:文件不合法 " );
        }
    }

    //安装APK的方法
    private void installAPK(File file) {
        Log.e("++++", "installAPK:进行安装 " );
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//启动另一个任务栈
        startActivityForResult(intent,INTENT_APK);
        System.exit(0);//安装完成后 退出即可
    }
}
10.主布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/mian_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
    <Button
        android:id="@+id/mian_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="版本检查"/>

</LinearLayout>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值