欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-03-26 20:08:40
android应用版本更新,断点下载并安装方法。 1.依赖: //《okhttp网络请求依赖》 implementation 'com.squareup.okhttp3:okhttp:...

android应用版本更新,断点下载并安装方法。

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{
    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>() {
        }.gettype();
        httputils.getinstance().getdata("https://www.xieast.com/api/checkversion.php", type, new netcallback() {
            //成功的方法
            @override
            public void success(object o) {
                //强转成我们所需的数据集合
                messagebean messagebean = (messagebean) 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="https://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>