搜了一下我的博客,发现之前零零散散写过很多关于版本更新用到的知识,包括下载,包括打开apk。但是并没有一套完整的版本更新的流程。正巧今天做公司项目的版本更新功能,索性总结一下。一方面方便我自己日后的使用,一方面给广大经验还不是很丰富的Android开发同仁一个借鉴。但不是每一步都会贴代码,因为很多步骤每个人的实现是不一样的,但我会说明每一步要做的事情。也方便大家去找寻对应的方法。
Step1:获取服务器版本
这步说白了就是请求服务器的一个接口,这个接口返回如下几个信息
- versioncode
- versionname
- 版本的说明,就是本次更新加了什么功能,有什么特色。一般由产品给出
- 是否强制更新,这个标记和后台商议
- 服务器最新版本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后会回到桌面,而不是显示打开和完成按钮的页面。
到这里版本更新基本上就讲全了。如果代码中引用了没有的提到的类,或者有什么问题,欢迎各位小伙伴们给予指正,我也会对这篇文章不断进行完善。