Android版本更新的一套完整的解决方案

搜了一下我的博客,发现之前零零散散写过很多关于版本更新用到的知识,包括下载,包括打开apk。但是并没有一套完整的版本更新的流程。正巧今天做公司项目的版本更新功能,索性总结一下。一方面方便我自己日后的使用,一方面给广大经验还不是很丰富的Android开发同仁一个借鉴。但不是每一步都会贴代码,因为很多步骤每个人的实现是不一样的,但我会说明每一步要做的事情。也方便大家去找寻对应的方法。

Step1:获取服务器版本

这步说白了就是请求服务器的一个接口,这个接口返回如下几个信息

  1. versioncode
  2. versionname
  3. 版本的说明,就是本次更新加了什么功能,有什么特色。一般由产品给出
  4. 是否强制更新,这个标记和后台商议
  5. 服务器最新版本app的下载地址

Step2:当我们访问接口成功后,就要比对服务器给的versioncode和本地的versioncode是否一致,如果服务器versioncode大于我们本地的versioncode,那就要更新。服务器的versioncode我们已经有了,那本地的如何获取。

public static int getVersionCode(Context context) {
        PackageManager packageManager = context.getPackageManager();
        try {
            PackageInfo packageInfo = packageManager.getPackageInfo(
                    context.getPackageName(), 0);
            return packageInfo.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 0;
}

我们可以把这个方法写入一个公共的工具类中,使用的时候直接调用,就可以获取到当前app的版本号。

Step3:如果本地版本号小于服务器的版本号,那么说明服务器上有了新的安装包,那么我们就要请求动态权限。这个也不给出具体代码,一般每个框架都有他不同的获取方法。需要获取的是SD卡的读取权限和写入权限。另外清单文件中也要配置

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

当然,这些有可能你已经配置了。如果配置过请忽略。

Step4:这里我们要弹出一个弹框,就是一个确认取消的对话框。

使用代码如下:

final CommonDialog commonDialog = new CommonDialog(this);
commonDialog.setTitleText("版本更新")                       

.setContentText(versionUpdateBean.getData().getBbms().replaceAll("\\\\n","\n"))
                 //这个setOne一会儿会着重说一下
                .setOne(versionUpdateBean.getData().getSfqzgx()==0?true:false)
                .setOneColor(0xFF00A684)
                .setTwoColor(0xFFFFFFFF)
                .setTitleSize(20)
                .setContentSize(14)
                .setOneSize(14)
                .setTwoSize(14)
                .setOneDrawable(0xFFE8EFEE)
                .setTwoDrawable(0xFF19BD9B)
                .setTwoClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //这里是点击确定的操作,一会我们第五步再说。
                    }
                })
                .show();
commonDialog.setCancelable(false);

这里也把CommonDialog的这个对话框贴出来。这是一个可以公用的对话框。在其他地方也可以使用,其中的主要元素包括头部的一个图片,图片下面有一个标题,标题下面有内容,最下面是确认取消。图片设置什么,标题、内容和按钮的文字大小、颜色和内容都可以设置。按钮的背景色也可以设置。还可以设置是一个按钮还是两个按钮。

CommonDialog代码如下

/**
 * 通用双按钮带图确认取消弹框
 * @author WaterWood
 */
public class CommonDialog extends Dialog {

    /**
     * 上下文
     */
    private Context context;
    /**
     * 控件
     */
    private ImageView iv_pic;
    private TextView tv_title;
    private TextView tv_content;
    private TextView tv_one;
    private TextView tv_two;
    /**
     * 图片资源
     */
    private int picResource;
    /**
     * 标题的内容
     */
    private String titleStr;
    /**
     * 标题的颜色
     */
    private int titleColor;
    /**
     * 标题的颜色
     */
    private int titleColorRes;
    /**
     * 标题的大小
     */
    private int titleSizeDp;
    /**
     * 内容的内容
     */
    private String contentStr;
    /**
     * 内容的颜色
     */
    private int contentColor;
    /**
     * 内容的颜色
     */
    private int contentColorRes;
    /**
     * 内容的大小
     */
    private int contentSizeDp;
    /**
     * 一按钮的内容
     */
    private String oneStr;
    /**
     * 一按钮的颜色
     */
    private int oneColor;
    /**
     * 一按钮的颜色
     */
    private int oneColorRes;
    /**
     * 一按钮的大小
     */
    private int oneSizeDp;
    /**
     * 是否是一个按钮 true:一个  false:两个
     */
    private boolean isOne;
    /**
     * 二按钮的内容
     */
    private String twoStr;
    /**
     * 二按钮的颜色
     */
    private int twoColor;
    /**
     * 二按钮的颜色
     */
    private int twoColorRes;
    /**
     * 二按钮的大小
     */
    private int twoSizeDp;
    /**
     * 一按钮背景
     */
    private GradientDrawable bgOne;
    /**
     * 二按钮背景
     */
    private GradientDrawable bgTwo;
    /**
     * 一按钮点击事件
     */
    private View.OnClickListener listenerOne;
    /**
     * 二按钮点击事件
     */
    private View.OnClickListener listenerTwo;

    public CommonDialog(Context context) {
        super(context);
        this.context = context;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        setContentView(R.layout.dialog_common);
        iv_pic = findViewById(R.id.iv_pic);
        tv_title = findViewById(R.id.tv_title);
        tv_content = findViewById(R.id.tv_content);
        tv_one = findViewById(R.id.tv_one);
        tv_two = findViewById(R.id.tv_two);
    }

    /**
     * 设置图片资源
     *
     * @param pic
     * @return
     */
    public CommonDialog setPic(int pic) {
        picResource = pic;
        return this;
    }

    /**
     * 设置标题的内容
     *
     * @param title
     * @return
     */
    public CommonDialog setTitleText(String title) {
        titleStr = title;
        return this;
    }

    /**
     * 设置标题的颜色,用0xFFFFFFFF格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setTitleColor(@ColorInt int colorName) {
        titleColor = colorName;
        return this;
    }

    /**
     * 设置标题的颜色,用R.color.black的格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setTitleColorRes(@ColorRes int colorName) {
        titleColorRes = colorName;
        return this;
    }

    /**
     * 设置标题的大小。这里我们传入一个dp的值
     *
     * @param size
     * @return
     */
    public CommonDialog setTitleSize(int size) {
        titleSizeDp = size;
        return this;
    }

    /**
     * 设置内容的内容
     *
     * @param title
     * @return
     */
    public CommonDialog setContentText(String title) {
        contentStr = title;
        return this;
    }

    /**
     * 设置内容的颜色,用0xFFFFFFFF格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setContentColor(@ColorInt int colorName) {
        contentColor = colorName;
        return this;
    }

    /**
     * 设置内容的颜色,用R.color.black的格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setContentColorRes(@ColorRes int colorName) {
        contentColorRes = colorName;
        return this;
    }

    /**
     * 设置内容的大小。这里我们传入一个dp的值
     *
     * @param size
     * @return
     */
    public CommonDialog setContentSize(int size) {
        contentSizeDp = size;
        return this;
    }

    /**
     * 设置一按钮的内容
     *
     * @param title
     * @return
     */
    public CommonDialog setOneText(String title) {
        oneStr = title;
        return this;
    }

    /**
     * 设置一按钮的颜色,用0xFFFFFFFF格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setOneColor(@ColorInt int colorName) {
        oneColor = colorName;
        return this;
    }

    /**
     * 设置一按钮的颜色,用R.color.black的格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setOneColorRes(@ColorRes int colorName) {
        oneColorRes = colorName;
        return this;
    }

    /**
     * 设置一按钮的大小。这里我们传入一个dp的值
     *
     * @param size
     * @return
     */
    public CommonDialog setOneSize(int size) {
        oneSizeDp = size;
        return this;
    }

    /**
     * 设置是否是一个按钮
     *
     * @return
     */
    public CommonDialog setOne(boolean flag) {
        isOne = flag;
        return this;
    }

    /**
     * 设置二按钮的内容
     *
     * @param title
     * @return
     */
    public CommonDialog setTwoText(String title) {
        twoStr = title;
        return this;
    }

    /**
     * 设置二按钮的颜色,用0xFFFFFFFF格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setTwoColor(@ColorInt int colorName) {
        twoColor = colorName;
        return this;
    }

    /**
     * 设置二按钮的颜色,用R.color.black的格式
     *
     * @param colorName
     * @return
     */
    public CommonDialog setTwoColorRes(@ColorRes int colorName) {
        twoColorRes = colorName;
        return this;
    }

    /**
     * 设置二按钮的大小。这里我们传入一个dp的值
     *
     * @param size
     * @return
     */
    public CommonDialog setTwoSize(int size) {
        twoSizeDp = size;
        return this;
    }

    /**
     * 设置按钮一的背景
     * @param colorName
     * @return
     */
    public CommonDialog setOneDrawable(@ColorInt int colorName) {
        bgOne = new GradientDrawable();
        bgOne.setShape(GradientDrawable.RECTANGLE);
        bgOne.setColor(colorName);
        bgOne.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, CommonUtils.dp2px(context,10),  CommonUtils.dp2px(context,10)});
        return this;
    }

    /**
     * 设置按钮二的背景
     * @param colorName
     * @return
     */
    public CommonDialog setTwoDrawable(@ColorInt int colorName) {
        bgTwo = new GradientDrawable();
        bgTwo.setShape(GradientDrawable.RECTANGLE);
        bgTwo.setColor(colorName);
        if (isOne){
            bgTwo.setCornerRadii(new float[]{0, 0, 0, 0, CommonUtils.dp2px(context, 10), CommonUtils.dp2px(context, 10), CommonUtils.dp2px(context, 10), CommonUtils.dp2px(context, 10)});
        }else {
            bgTwo.setCornerRadii(new float[]{0, 0, 0, 0, CommonUtils.dp2px(context, 10), CommonUtils.dp2px(context, 10), 0, 0});
        }
        return this;
    }

    /**
     * 设置一按钮点击事件
     * @param listener
     * @return
     */
    public CommonDialog setOneClickListener(View.OnClickListener listener){
        listenerOne = listener;
        return this;
    }

    /**
     * 设置二按钮点击事件
     * @param listener
     * @return
     */
    public CommonDialog setTwoClickListener(View.OnClickListener listener){
        listenerTwo = listener;
        return this;
    }

    @Override
    public void show() {
        super.show();
        //设置图片显示
        if (picResource != 0) {
            iv_pic.setImageResource(picResource);
            iv_pic.setVisibility(View.VISIBLE);
        }
        //设置标题显示
        if (!NullUtil.isStringEmpty(titleStr)) {
            tv_title.setVisibility(View.VISIBLE);
            tv_title.setText(titleStr);
            //文字的颜色只有一个生效,并且要在文字可以显示的基础上才能改变
            if (titleColor != 0) {
                tv_title.setTextColor(titleColor);
            } else if (titleColorRes != 0) {
                tv_title.setTextColor(titleColorRes);
            }
            //文字的大小,并且要在文字可以显示的基础上才能改变
            if (titleSizeDp != 0) {
                tv_title.setTextSize(titleSizeDp);
            }
        }
        //设置内容显示
        if (!NullUtil.isStringEmpty(contentStr)) {
            tv_content.setVisibility(View.VISIBLE);
            tv_content.setText(contentStr);
            //文字的颜色只有一个生效,并且要在文字可以显示的基础上才能改变
            if (contentColor != 0) {
                tv_content.setTextColor(contentColor);
            } else if (contentColorRes != 0) {
                tv_content.setTextColor(contentColorRes);
            }
            //文字的大小,并且要在文字可以显示的基础上才能改变
            if (contentSizeDp != 0) {
                tv_content.setTextSize(contentSizeDp);
            }
        }
        //判断是否是一个按钮
        if (isOne) {
            tv_one.setVisibility(View.GONE);
        }
        //设置一按钮显示
        if (!NullUtil.isStringEmpty(oneStr)) {
            tv_one.setText(oneStr);
        }
        //文字的颜色只有一个生效,并且要在文字可以显示的基础上才能改变
        if (oneColor != 0) {
            tv_one.setTextColor(oneColor);
        } else if (oneColorRes != 0) {
            tv_one.setTextColor(oneColorRes);
        }
        //文字的大小,并且要在文字可以显示的基础上才能改变
        if (oneSizeDp != 0) {
            tv_one.setTextSize(oneSizeDp);
        }
        //设置二按钮显示
        if (!NullUtil.isStringEmpty(twoStr)) {
            tv_two.setText(twoStr);
        }
        //文字的颜色只有一个生效,并且要在文字可以显示的基础上才能改变
        if (twoColor != 0) {
            tv_two.setTextColor(twoColor);
        } else if (twoColorRes != 0) {
            tv_two.setTextColor(twoColorRes);
        }
        //文字的大小,并且要在文字可以显示的基础上才能改变
        if (twoSizeDp != 0) {
            tv_two.setTextSize(twoSizeDp);
        }
        if (bgOne!=null){
            tv_one.setBackgroundDrawable(bgOne);
        }
        if (bgTwo!=null){
            tv_two.setBackgroundDrawable(bgTwo);
        }
        if (listenerOne != null) {
            tv_one.setOnClickListener(listenerOne);
        }else{
            tv_one.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dismiss();
                }
            });
        }
        if (listenerTwo!=null){
            tv_two.setOnClickListener(listenerTwo);
        }
    }
}

这是对应的布局R.layout.dialog_common

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">
    <LinearLayout
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_dialog_corner"
        android:layout_centerInParent="true"
        android:orientation="vertical">

        <View
            android:layout_width="match_parent"
            android:layout_height="10dp"/>

        <ImageView
            android:id="@+id/iv_pic"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:visibility="gone"/>

        <TextView
            android:text="我是标题"
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textColor="#333333"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:visibility="gone"/>

        <TextView
            android:text="我是内容"
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="#666666"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:visibility="gone"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"/>

        <View
            android:layout_width="match_parent"
            android:layout_height="20dp"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_one"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:textSize="14sp"
                android:gravity="center"
                android:textColor="#666666"
                android:text="取消"/>
            <TextView
                android:id="@+id/tv_two"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="确认"
                android:textColor="#333333"
                android:textSize="14sp"
                android:gravity="center"/>
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

其中使用了一个背景@drawable/bg_dialog_corner

 

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="10dp"/>
    <solid android:color="@color/white"/>
</shape>

 

里面用到了两个我自己写的类,一个是CommonUtils.dp2px,代码如下

public static int dp2px(Context context, float dpval) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpval, context.getResources().getDisplayMetrics());
}

这是一个dp转px的工具方法,放入公共工具类中进行调用就好。还有一个是NullUtil工具类,这个类是一个判空的类,可以用来判空字符串、TextView、list,很好用的工具类,我也贴在这里。

/**
 * 空判断
 * @author WaterWood
 */
public class NullUtil {
    public static boolean isStringEmpty(String str){
        return str==null||"".equals(str)||"null".equalsIgnoreCase(str);
    }

    public static boolean isTextEmpty(TextView textView) {
        return isStringEmpty(textView.getText().toString().trim());
    }

    public static boolean isListEmpty(List list){
        return list==null||list.size()<=0;
    }
}

最后要说明一下CommonDialog调用时的一个setOne方法,如果传入true,只会显示一个按钮;不调用,或者传入false,则是显示两个按钮。如果后台返回本次更新是强制更新,那我们调用setOne传入true;如果不是强制更新,则传入false。

另外在CommonDialog调用setContentText这个方法时,我们看到里面放了一长串,这个是因为我们的版本描述一般都是很多行,但服务器放在数据库的内容只能是一行,所以后台放的内容是这样:

版本说明:\n1.添加了删除数据记录的功能。\n2.处理缺陷进行优化,更加便捷。\n3.修改了弹窗样式,简化操作

但是回来就变了,因为经过gson转换后多了很多转义字符,让我们的字符串显示出了带\n的内容,所以我们通过我代码中的处理可以规避这个现象。

到这里第四步就结束了。

Step5:在点击了确定后,用户要开始下载apk,那么这里就有两样东西,一个是带进度框的弹窗,一个是下载的方法。首先先把点击确定时我们的调用代码贴出来

//弹出进度对话框
downloadDialog = new DownloadDialog(LoginActivity.this);
downloadDialog.setContent("安装包下载中,请稍后...")
              .show();
//进行下载工作,并修改对话框对应的值,下载完成后打开apk
downLoad();

全局定义

private DownloadDialog downloadDialog;

因为一会儿我们还要再别的地方操作这个弹框。

接着我把DownloadDialog贴出来

/**
 * 通用带进度条弹框
 * @author WaterWood
 */
public class DownloadDialog extends Dialog {

    /**
     * 上下文
     */
    private Context context;
    /**
     * 控件
     */
    private HorizontalProgressBarWithNumber progress_bar;
    private TextView tv_content;
    /**
     * 显示的文字内容
     */
    private String text;
    /**
     * 显示文字的大小
     */
    private float size;
    /**
     * 显示文字的颜色
     */
    private int color;

    public DownloadDialog(Context context) {
        super(context);
        this.context = context;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        setContentView(R.layout.dialog_progress_download);
        progress_bar = findViewById(R.id.progress_bar);
        tv_content = findViewById(R.id.tv_content);
    }

    /**
     * 设置文字内容
     */
    public DownloadDialog setContent(String str){
        text = str;
        return this;
    }

    /**
     * 设置文字的大小
     * @param size
     * @return
     */
    public DownloadDialog setContentSize(float size){
        this.size = size;
        return this;
    }

    /**
     * 设置文字的颜色
     * @param color
     * @return
     */
    public DownloadDialog setContentColor(int color){
        this.color = color;
        return this;
    }

    /**
     * 设置进度条进度方法,这个不在构建者里,单独调用
     * @param progress
     */
    public void setProgress(int progress){
        progress_bar.setProgress(progress);
    }

    @Override
    public void show() {
        super.show();
        if (!NullUtil.isStringEmpty(text)){
            tv_content.setText(text);
        }
        if (size!=0){
            tv_content.setTextSize(size);
        }
        if (color!=0){
            tv_content.setTextColor(color);
        }
    }
}

布局 R.layout.dialog_progress_download如下

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <RelativeLayout
        android:layout_width="300dp"
        android:layout_height="100dp"
        android:background="@drawable/bg_dialog_corner"
        android:layout_centerInParent="true">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_centerInParent="true">
            <com.hao.baselib.widge.HorizontalProgressBarWithNumber
                android:id="@+id/progress_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginLeft="50dp"
                android:layout_marginRight="40dp"/>

            <TextView
                android:id="@+id/tv_content"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="进度条弹框"
                android:textColor="#333333"
                android:textSize="14sp"
                android:layout_marginTop="20dp"
                android:layout_gravity="center_horizontal"/>
        </LinearLayout>
    </RelativeLayout>
</RelativeLayout>

布局中用到的背景和之前的CommonDialog是一样的。 

这个dialog就简单多了,就一个进度条和一行文字。文字也是可以自己定义内容,大小和颜色。进度条使用了一个自定义控件。

/**
 * 自定义的进度条
 * @author WaterWood
 */
public class HorizontalProgressBarWithNumber extends ProgressBar {
    private static final int DEFAULT_TEXT_SIZE = 10;
    private static final int DEFAULT_TEXT_COLOR = 0XFF19BE9B;
    private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFC8F0E8;
    private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 11;
    private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 11;
    private static final int DEFAULT_SIZE_TEXT_OFFSET = 10;

    /**
     * painter of all drawing things
     */
    protected Paint mPaint = new Paint();
    /**
     * color of progress number
     */
    protected int mTextColor = DEFAULT_TEXT_COLOR;
    /**
     * size of text (sp)
     */
    protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);

    /**
     * offset of draw progress
     */
    protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET);

    /**
     * height of reached progress bar
     */
    protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR);

    /**
     * color of reached bar
     */
    protected int mReachedBarColor = DEFAULT_TEXT_COLOR;
    /**
     * color of unreached bar
     */
    protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR;
    /**
     * height of unreached progress bar
     */
    protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);
    /**
     * view width except padding
     */
    protected int mRealWidth;

    protected boolean mIfDrawText = true;

    protected static final int VISIBLE = 0;

    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs,
                                           int defStyle) {
        super(context, attrs, defStyle);

        setHorizontalScrollBarEnabled(true);

        obtainStyledAttributes(attrs);

        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    /**
     * get the styled attributes
     *
     * @param attrs
     */
    private void obtainStyledAttributes(AttributeSet attrs) {
        // init values from custom attributes
        final TypedArray attributes = getContext().obtainStyledAttributes(
                attrs, R.styleable.HorizontalProgressBarWithNumber);

        mTextColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_color,
                        DEFAULT_TEXT_COLOR);
        mTextSize = (int) attributes.getDimension(
                R.styleable.HorizontalProgressBarWithNumber_progress_text_size,
                mTextSize);

        mReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,
                        mTextColor);
        mUnReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,
                        DEFAULT_COLOR_UNREACHED_COLOR);
        mReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,
                        mReachedProgressBarHeight);
        mUnReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,
                        mUnReachedProgressBarHeight);
        mTextOffset = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,
                        mTextOffset);

        int textVisible = attributes
                .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,
                        VISIBLE);
        if (textVisible != VISIBLE) {
            mIfDrawText = false;
        }
        attributes.recycle();
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec,
                                          int heightMeasureSpec) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY) {

            float textHeight = (mPaint.descent() + mPaint.ascent());
            int exceptHeight = (int) (getPaddingTop() + getPaddingBottom() + Math
                    .max(Math.max(mReachedProgressBarHeight,
                            mUnReachedProgressBarHeight), Math.abs(textHeight)));

            heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,
                    MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        canvas.save();
        //画笔平移到指定paddingLeft, getHeight() / 2位置,注意以后坐标都为以此为0,0
        canvas.translate(getPaddingLeft()+20, getHeight() / 2);
        //当前进度和总值的比例
        float radio = getProgress() * 1.0f / getMax();
        //已到达的宽度
        float progressPosX = (int) ((mRealWidth - mTextOffset - 100) * radio);
        //绘制的文本
        String text = getProgress() + "%";

        //拿到字体的宽度和高度
        float textWidth = mPaint.measureText(text);
        float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;

        //如果到达最后,则未到达的进度条不需要绘制
        if (progressPosX > mRealWidth) {
            progressPosX = mRealWidth;
        }

        float endX = progressPosX;

        // 绘制背景线
        mPaint.setColor(mUnReachedBarColor);
        mPaint.setStrokeWidth(mReachedProgressBarHeight);
        canvas.drawLine(0, 0, mRealWidth - mTextOffset - 100, 0, mPaint);

        // 绘制到达线
        mPaint.setColor(mReachedBarColor);
        mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
        canvas.drawLine(0, 0, endX, 0, mPaint);

        // 绘制文本
        if (mIfDrawText) {
            mPaint.setColor(mTextColor);
            canvas.drawText(text, mRealWidth - mTextOffset - 65, -textHeight, mPaint);
        }

        canvas.restore();

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRealWidth = w - getPaddingRight() - getPaddingLeft();

    }

    /**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     *
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());

    }
}

其中用到一个属性配置attr_progress_bar.xml这个写在value下

<declare-styleable name="HorizontalProgressBarWithNumber">
        <attr name="progress_unreached_color" format="color" />
        <attr name="progress_reached_color" format="color" />
        <attr name="progress_reached_bar_height" format="dimension" />
        <attr name="progress_unreached_bar_height" format="dimension" />
        <attr name="progress_text_size" format="dimension" />
        <attr name="progress_text_color" format="color" />
        <attr name="progress_text_offset" format="dimension" />
        <attr name="progress_text_visibility" format="enum">
            <enum name="visible" value="0" />
            <enum name="invisible" value="1" />
        </attr>
    </declare-styleable>

在downLoad方法中

DownloadUtil.get().download(versionUpdateBean.getData().getAddress(), pathDir, new DownloadUtil.OnDownloadListener() {
            @Override
            public void onDownloadSuccess(final String path) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //消失进度框
                        downloadDialog.dismiss();
                        Log.i("myDownload", "下载成功");
                        //打开apk
                        startInstall(path);
                    }
                });
            }

            @Override
            public void onDownloading(final int progress) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //设置进度
                        downloadDialog.setProgress(progress);
                    }
                });
            }

            @Override
            public void onDownloadFailed() {
                //失败
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //消失对话框
                        downloadDialog.dismiss();
                        //吐司下载失败
                        CommonUtils.toastWord(LoginActivity.this, "下载失败");
                        //弹出版本更新对话框
                        commonDialog();
                    }
                });
            }
        });

其实就是调用了下载器

其中第一个参数就是服务器返回的apk下载地址,第二个参数是本地保存路径,地址如下

private String pathDir = Environment.getExternalStorageDirectory() + File.separator + "longway" + File.separator + "apk" + File.separator;
    

第三个就是我们的回调。回调中,onDownloading需要我们更改进度条的进度。onDownloadSuccess是成功的方法。onDownloadFailed失败后需要我们进行下载重试的操作。

onDownloadSuccess中我们调用了打开apk的方法startInstall,这里贴出代码

/**
     * 打开apk
     *
     * @param filePath
     */
    private void startInstall(String filePath) {
        //分别进行7.0以上和7.0以下的尝试
        File apkfile = new File(filePath);
        if (!apkfile.exists()) {
            return;
        }
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //7.0以上
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(getApplicationContext(), "com.hao.baselib.fileProvider2", apkfile);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");
        }
        startActivity(intent);
    }

注意,Android7.0加大了对操作文件权限的控制,我们需要放开对应文件夹的权限。我们这里用的是Environment.getExternalStorageDirectory() + File.separator + "longway" + File.separator + "apk" + File.separator路径,所以,我的权限配置是这样的,

在清单文件中,

<provider
    android:name="com.hao.baselib.fileprovider.ApkFileProvider"
    android:authorities="com.hao.baselib.fileProvider2"
    android:exported="false"
    android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths_apk" />
</provider>

其中file_paths_apk在res/xml路径下

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <paths>
        <external-path path="longway" name="external_storage_root"/>
    </paths>
</resources>

需要注意一点,

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

这两句,使用的是add,不是set,如果使用set会覆盖掉前一句设置的。而其中intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);如果不设置,我们安装完app后会回到桌面,而不是显示打开和完成按钮的页面。

到这里版本更新基本上就讲全了。如果代码中引用了没有的提到的类,或者有什么问题,欢迎各位小伙伴们给予指正,我也会对这篇文章不断进行完善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值