上一篇:Android 天气APP(二十五)地图天气(下)嵌套滑动布局渲染天气数据
效果图
1.开发前言
对于任何APP来说基本上都会有自动更新这个功能,那么为什么我的这个APP之前没有写呢?因为之前APP比较小,更像是一个Demo,看的人没有几个,更像是我在自娱自乐,不过随着我不断的更新APP的功能和写博客,使用的用户增加了,虽然不多,但我还是蛮开心的,增加APP自动更新这个功能的好处就是,无论以后我更新了什么功能,只要你打开APP就会提醒你新增的功能,你可以安装使用,也可以不安装都随你。说了这么多废话了,还是来实践吧。
2.上传应用到分发平台
作为个体开发者,我的数据都来源于网络API,没有自己的服务器和数据库,而且我也没有上架到应用市场,因为现在个人开发者上架应用市场需要的东西比较多,一时半会也搞不了,所以退而求其次,使用分发平台,这个平台首先是免费的,其次比较方便去测试,常规的就是蒲公英、闪红、Fir.im等一些,我这里用的是Fir.im,可以用邮箱注册,然后实名认证通过之后,你就可以上传应用上去了。那些步骤都属于基本操作,我就不过多赘述,重点来看版本更新的接口。往下进行时,请先确保平台上有你上传得APK才行。
点击应用检测更新接口
实际上我需要的只是id和token
先来看id,点击这个应用的图标,然后点击基本信息,下面就会看到应用id
再来看api_token,鼠标悬停在你的用户名上面,选择API token
如果你是第一次使用,那么会让你先生成一个随机的API Token,复制即可,记住不要乱改。
现在该有的都有了。我们看看它的返回示例是怎么样的。这里我就把我的更新API接口放到这里,
http://api.bq04.com/apps/latest/5e8c37e90d81cc0db2645c1c?api_token=468e4653ca9e1d34e7a73b8f2d7191da
到浏览器打开,你会看到这样的数据
然后再格式化一下
{
"name": "好天气",
"version": "1",
"changelog": "新增自动更新",
"updated_at": 1598861611,
"versionShort": "2.2",
"build": "1",
"installUrl": "https://download.jappstore.com/apps/5e8c37e90d81cc0db2645c1c/install?download_token=dccf478d29848e406f371890ac761bfb\u0026source=update",
"install_url": "https://download.jappstore.com/apps/5e8c37e90d81cc0db2645c1c/install?download_token=dccf478d29848e406f371890ac761bfb\u0026source=update",
"direct_install_url": "https://download.jappstore.com/apps/5e8c37e90d81cc0db2645c1c/install?download_token=dccf478d29848e406f371890ac761bfb\u0026source=update",
"update_url": "http://jappstore.com/h4sg",
"binary": {
"fsize": 31071757
}
}
3.版本数据请求与存储
可以看到该有的数据都有,OK,现在先来生成这个实体。这里我会把数据放到数据库里,方便随时调用。所以在mvplibrary下面的bean包下新建一个AppVersion,继承 LitePalSupport
代码如下:
package com.llw.mvplibrary.bean;
import org.litepal.crud.LitePalSupport;
/**
* 应用版本更新
*/
public class AppVersion extends LitePalSupport {
/**
* name : 好天气
* version : 1
* changelog : 新增地图天气
* updated_at : 1598581575
* versionShort : 2.1
* build : 1
* installUrl : https://download.jappstore.com/apps/5e8c37e90d81cc0db2645c1c/install?download_token=dccf478d29848e406f371890ac761bfb&source=update
* install_url : https://download.jappstore.com/apps/5e8c37e90d81cc0db2645c1c/install?download_token=dccf478d29848e406f371890ac761bfb&source=update
* direct_install_url : https://download.jappstore.com/apps/5e8c37e90d81cc0db2645c1c/install?download_token=dccf478d29848e406f371890ac761bfb&source=update
* update_url : http://jappstore.com/h4sg
* binary : {"fsize":31038590}
*/
private String name;
private String version;
private String changelog;
private int updated_at;
private String versionShort;
private String build;
private String installUrl;
private String install_url;
private String direct_install_url;
private String update_url;
private String appSize;
public String getAppSize() {
return appSize;
}
public void setAppSize(String appSize) {
this.appSize = appSize;
}
private BinaryBean binary;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getChangelog() {
return changelog;
}
public void setChangelog(String changelog) {
this.changelog = changelog;
}
public int getUpdated_at() {
return updated_at;
}
public void setUpdated_at(int updated_at) {
this.updated_at = updated_at;
}
public String getVersionShort() {
return versionShort;
}
public void setVersionShort(String versionShort) {
this.versionShort = versionShort;
}
public String getBuild() {
return build;
}
public void setBuild(String build) {
this.build = build;
}
public String getInstallUrl() {
return installUrl;
}
public void setInstallUrl(String installUrl) {
this.installUrl = installUrl;
}
public String getInstall_url() {
return install_url;
}
public void setInstall_url(String install_url) {
this.install_url = install_url;
}
public String getDirect_install_url() {
return direct_install_url;
}
public void setDirect_install_url(String direct_install_url) {
this.direct_install_url = direct_install_url;
}
public String getUpdate_url() {
return update_url;
}
public void setUpdate_url(String update_url) {
this.update_url = update_url;
}
public BinaryBean getBinary() {
return binary;
}
public void setBinary(BinaryBean binary) {
this.binary = binary;
}
public static class BinaryBean {
/**
* fsize : 31038590
*/
private int fsize;
public int getFsize() {
return fsize;
}
public void setFsize(int fsize) {
this.fsize = fsize;
}
}
}
然后改动一下litepal.xml配置文件
数据表有了,下面就是要请求数据了,所以要新增一个API接口,打开ServiceGenerator,新增如下,我将之前的测试地址做了一个拆分,这样规范管理,虽说有些麻烦。
case 5://APP更新
BASE_URL = "http://api.bq04.com";//Fr.im更新
break;
然后打开ApiService,新增接口方法,因为里面的id和api token是固定的所以我就不用动态传递过去了,直接写死在url里面,当然如果你要写的话记得要用自己的id和api token,用我的你是那不到返回数据的。
/**
* APP版本更新
*/
@GET("/apps/latest/你的id?api_token=你的API Token")
Call<AppVersion> getAppInfo();
那么下面就要写一个订阅器了,我的想法是在应用欢迎页就请求数据。那么就在app下的contract包下创建一个SplashContract,里面的代码如下:
package com.llw.goodweather.contract;
import com.llw.goodweather.api.ApiService;
import com.llw.goodweather.bean.NewSearchCityResponse;
import com.llw.goodweather.bean.NowResponse;
import com.llw.mvplibrary.base.BasePresenter;
import com.llw.mvplibrary.base.BaseView;
import com.llw.mvplibrary.bean.AppVersion;
import com.llw.mvplibrary.net.NetCallBack;
import com.llw.mvplibrary.net.ServiceGenerator;
import retrofit2.Call;
import retrofit2.Response;
/**
* 欢迎页订阅器
*/
public class SplashContract {
public static class SplashPresenter extends BasePresenter<ISplashView> {
/**
* 获取最新的APP版本信息
*/
public void getAppInfo() {//注意这里的4表示新的搜索城市地址接口
ApiService service = ServiceGenerator.createService(ApiService.class, 5);
service.getAppInfo().enqueue(new NetCallBack<AppVersion>() {
@Override
public void onSuccess(Call<AppVersion> call, Response<AppVersion> response) {
if(getView() != null){
getView().getAppInfoResult(response);
}
}
@Override
public void onFailed() {
if(getView() != null){
getView().getDataFailed();
}
}
});
}
}
public interface ISplashView extends BaseView {
//APP信息返回
void getAppInfoResult(Response<AppVersion> response);
//错误返回
void getDataFailed();
}
}
都是属于基本操作了,不需要解释了。
然后打开SplashActivity
增加请求
实现如下三个方法
@Override
protected SplashContract.SplashPresenter createPresent() {
return new SplashContract.SplashPresenter();
}
/**
* 获取APP最新版本信息返回
* @param response
*/
@Override
public void getAppInfoResult(Response<AppVersion> response) {
if (response.body() != null) {
AppVersion appVersion = new AppVersion();
appVersion.setName(response.body().getName());//应用名称
appVersion.setVersion(response.body().getVersion());//应用版本 对应code
appVersion.setVersionShort(response.body().getVersionShort());//应用版本名
appVersion.setChangelog(response.body().getChangelog());//更新日志
appVersion.setUpdate_url(response.body().getUpdate_url());//更新地址
appVersion.setInstall_url(response.body().getInstall_url());//安装地址
appVersion.setAppSize(String.valueOf(response.body().getBinary().getFsize()));//APK大小
//添加数据前先判断是否已经有数据了
if (LitePal.find(AppVersion.class, 1) != null){
appVersion.update(1);//更新数据
}else {
appVersion.save();//保存添加数据
}
}
}
@Override
public void getDataFailed() {
Log.d("Network Error","网络异常");
}
现在这个数据就已经有了并且也储存起来了,下一步就是检测这个版本的更新了。
4.检查版本更新、自定义更新提示弹窗
这里我写了两个工具类,代码如下
APKVersionInfoUtils .java
package com.llw.goodweather.utils;
import android.content.Context;
import android.content.pm.PackageManager;
/**
* Created by chenxi on 2019/4/29.
* APP版本信息获取工具类
*/
public class APKVersionInfoUtils {
/**
* 获取当前本地apk的版本
*
* @param mContext
* @return
*/
public static int getVersionCode(Context mContext) {
int versionCode = 0;
try {
//获取软件版本号,对应AndroidManifest.xml下android:versionCode
versionCode = mContext.getPackageManager().
getPackageInfo(mContext.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return versionCode;
}
/**
* 获取版本号名称
*
* @param context 上下文
* @return
*/
public static String getVerName(Context context) {
String verName = "";
try {
verName = context.getPackageManager().
getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return verName;
}
}
AppStartUpUtils .java
package com.llw.goodweather.utils;
import android.content.Context;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* APP启动判断工具类
*/
public class AppStartUpUtils {
/**
* 判断是否是首次启动
* @param context
* @return
*/
public static boolean isFirstStartApp(Context context) {
Boolean isFirst = SPUtils.getBoolean(Constant.APP_FIRST_START, true, context);
if (isFirst) {// 第一次
SPUtils.putBoolean(Constant.APP_FIRST_START, false, context);
return true;
} else {
return false;
}
}
/**
* 判断是否是今日首次启动APP
* @param context
* @return
*/
public static boolean isTodayFirstStartApp(Context context) {
String saveDate = SPUtils.getString(Constant.START_UP_APP_TIME, "2020-08-27", context);
String todayDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
if (!saveDate.equals(todayDate)) {//第一次打开
SPUtils.putString(Constant.START_UP_APP_TIME, todayDate, context);
return true;
} else {
return false;
}
}
}
第二个工具类里面用到了一些全局的参数,如下:
package com.llw.goodweather.utils;
/**
* 统一管理缓存中对应的KEY
*/
public class Constant {
public final static String SUCCESS_CODE = "200";
public final static String CITY = "city";//市
public final static String DISTRICT = "district";//区/县
public final static String LOCATION_ID = "locationId";//通过搜索接口得到的城市id,在V7中所有数据通过id来查询
public final static String EVERYDAY_IMG = "everyday_img";//每日图片开关
public final static String IMG_LIST = "img_list";//图片列表开关
public final static String CUSTOM_IMG = "custom_img";//手动定义开关
public final static String IMG_POSITION = "img_position";//选中的本地背景图片
public final static int SELECT_PHOTO = 2;//启动相册标识
public final static String CUSTOM_IMG_PATH = "custom_img_path";//手动上传图片地址
public final static String FLAG_OTHER_RETURN="flag_other_return";//跳转页面的标识
public final static String LOCATION="location";
public final static String APP_FIRST_START = "appFirstStart";//App首次启动
public final static String START_UP_APP_TIME = "startAppTime";//今日启动APP的时间
}
然后在MainActivity中就可以进行判断了
/**
* 检查APP版本
*/
private void checkAppVersion() {
AppVersion appVersion = LitePal.find(AppVersion.class,1);
if(!appVersion.getVersionShort().equals(APKVersionInfoUtils.getVerName(context))){//提示更新
if(AppStartUpUtils.isTodayFirstStartApp(context)){//今天第一次打开APP
//更新提示弹窗
}
}
}
下面来写这个更新提示弹窗,我就把我好久之前写的放上来了。
首先增加样式文件,在mvplibrary下面的styles.xml中
<!--自定义对话框-->
<style name="dialog" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowNoTitle">true</item>
</style>
<!--对话框弹出和消失动画-->
<style name="dialog_from_bottom_anim">
<item name="android:windowEnterAnimation">@anim/dialog_from_bottom_anim_in</item>
<item name="android:windowExitAnimation">@anim/dialog_from_bottom_anim_out</item>
</style>
<style name="dialog_from_top_anim">
<item name="android:windowEnterAnimation">@anim/dialog_from_top_anim_in</item>
<item name="android:windowExitAnimation">@anim/dialog_from_top_anim_out</item>
</style>
<style name="dialog_scale_anim">
<item name="android:windowEnterAnimation">@anim/dialog_scale_anim_in</item>
<item name="android:windowExitAnimation">@anim/dialog_scale_anim_out</item>
</style>
里面还有6个动画文件。如下:
在anim下创建
dialog_from_bottom_anim_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="400"
android:fromXDelta="0"
android:fromYDelta="1000"
android:toXDelta="0"
android:toYDelta="0" />
</set>
dialog_from_bottom_anim_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="400"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="1000" />
</set>
dialog_from_top_anim_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="1000"
android:fromYDelta="-100%"
android:toYDelta="0" />
</set>
dialog_from_top_anim_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="1000"
android:fromYDelta="0"
android:toYDelta="-100%" />
</set>
dialog_scale_anim_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<scale
android:duration="135"
android:fromXScale="0.8"
android:fromYScale="0.8"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.05"
android:toYScale="1.05" />
<scale
android:duration="105"
android:fromXScale="1.05"
android:fromYScale="1.05"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="135"
android:toXScale="0.95"
android:toYScale="0.95" />
<scale
android:duration="60"
android:fromXScale="0.95"
android:fromYScale="0.95"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="240"
android:toXScale="1.0"
android:toYScale="1.0" />
<alpha
android:duration="90"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>
dialog_scale_anim_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<scale
android:duration="150"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.6"
android:toYScale="0.6" />
<alpha
android:duration="150"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0" />
</set>
下面就是关于自定义Dialog的内容了。
在view下新增一个dialog文件夹,然后一一创建下面的文件
AlertDialog.java
package com.llw.mvplibrary.view.dialog;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import com.llw.mvplibrary.R;
/**
* 自定义弹窗
*/
public class AlertDialog extends Dialog {
private AlertController mAlert;
public AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, themeResId);
mAlert = new AlertController(this, getWindow());
}
public void setText(int viewId, CharSequence text) {
mAlert.setText(viewId, text);
}
public <T extends View> T getView(int viewId) {
return mAlert.getView(viewId);
}
public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {
mAlert.setOnClickListener(viewId, onClickListener);
}
//----------------------------------------------------------------------------------------------
public static class Builder {
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, R.style.dialog);
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(context, themeResId);
}
/**
* 设置对话框布局
*
* @param view
* @return
*/
public Builder setContentView(View view) {
P.mView = view;
P.mLayoutResId = 0;
return this;
}
/**
* @param layoutId
* @return
*/
public Builder setContentView(int layoutId) {
P.mView = null;
P.mLayoutResId = layoutId;
return this;
}
/**
* 设置文本
*
* @param viewId
* @param text
* @return
*/
public Builder setText(int viewId, CharSequence text) {
P.mTextArray.put(viewId, text);
return this;
}
/**
* 设置文本颜色
*
* @param viewId
* @param color
* @return
*/
public Builder setTextColor(int viewId, int color) {
P.mTextColorArray.put(viewId, color);
return this;
}
/**
* 设置图标
*
* @param iconId
* @return
*/
public Builder setIcon(int iconId, int resId) {
P.mIconArray.put(iconId, resId);
return this;
}
/**
* 设置图片
*
* @param viewId
* @return
*/
public Builder setBitmap(int viewId, Bitmap bitmap) {
P.mBitmapArray.put(viewId, bitmap);
return this;
}
/**
* 设置对话框宽度占满屏幕
*
* @return
*/
public Builder fullWidth() {
P.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
return this;
}
/**
* 对话框底部弹出
*
* @param isAnimation
* @return
*/
public Builder fromBottom(boolean isAnimation) {
if (isAnimation) {
P.mAnimation = R.style.dialog_from_bottom_anim;
}
P.mGravity = Gravity.BOTTOM;
return this;
}
/**
* 对话框右部弹出
*
* @param isAnimation
* @return
*/
public Builder fromRight(boolean isAnimation) {
if (isAnimation) {
P.mAnimation = R.style.dialog_from_bottom_anim;
}
P.mGravity = Gravity.RIGHT;
return this;
}
/**
* 设置对话框宽高
*
* @param width
* @param heigth
* @return
*/
public Builder setWidthAndHeight(int width, int heigth) {
P.mWidth = width;
P.mHeight = heigth;
return this;
}
/**
* 设置对话框宽高
*
* @param width
* @param heigth
* @return
*/
public Builder setWidthAndHeightMargin(int width, int heigth, int heightMargin, int widthMargin) {
P.mWidth = width;
P.mHeight = heigth;
P.mHeightMargin = heightMargin;
P.mWidthMargin = widthMargin;
return this;
}
/**
* 添加默认动画
*
* @return
*/
public Builder addDefaultAnimation() {
P.mAnimation = R.style.dialog_scale_anim;
return this;
}
/**
* 设置动画
*
* @param styleAnimation
* @return
*/
public Builder setAnimation(int styleAnimation) {
P.mAnimation = styleAnimation;
return this;
}
/**
* 设置点击事件
*
* @param viewId
* @param onClickListener
* @return
*/
public Builder setOnClickListener(int viewId, View.OnClickListener onClickListener) {
P.mClickArray.put(viewId, onClickListener);
return this;
}
public Builder setOnLongClickListener(int viewId, View.OnLongClickListener onLongClickListener) {
P.mLondClickArray.put(viewId, onLongClickListener);
return this;
}
/**
* Sets whether the dialog is cancelable or not. Default is true.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setCancelable(boolean cancelable) {
P.mCancelable = cancelable;
return this;
}
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
return this;
}
public Builder setOnDismissListener(OnDismissListener onDismissListener) {
P.mOnDismissListener = onDismissListener;
return this;
}
public Builder setOnKeyListener(OnKeyListener onKeyListener) {
P.mOnKeyListener = onKeyListener;
return this;
}
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
}
AlertController.java
package com.llw.mvplibrary.view.dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
/**
* 弹窗控制
*/
public class AlertController {
private AlertDialog mAlertDialog;
private Window mWindow;
private DialogViewHelper mViewHelper;
public AlertController(AlertDialog alertDialog, Window window) {
mAlertDialog = alertDialog;
mWindow = window;
}
public void setDialogViewHelper(DialogViewHelper dialogViewHelper) {
mViewHelper = dialogViewHelper;
}
public void setText(int viewId, CharSequence text) {
mViewHelper.setText(viewId, text);
}
public void setIcon(int viewId, int resId) {
mViewHelper.setIcon(viewId, resId);
}
public <T extends View> T getView(int viewId) {
return mViewHelper.getView(viewId);
}
public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {
mViewHelper.setOnClickListener(viewId, onClickListener);
}
public AlertDialog getDialog() {
return mAlertDialog;
}
public Window getWindow() {
return mWindow;
}
//-------------------------------------------------------------------------------------------------
public static class AlertParams {
public Context mContext;
//对话框主题背景
public int mThemeResId;
public boolean mCancelable;
public DialogInterface.OnCancelListener mOnCancelListener;
public DialogInterface.OnDismissListener mOnDismissListener;
public DialogInterface.OnKeyListener mOnKeyListener;
//文本颜色
public SparseArray<Integer> mTextColorArray = new SparseArray<>();
//存放文本的更改
public SparseArray<CharSequence> mTextArray = new SparseArray<>();
//存放点击事件
public SparseArray<View.OnClickListener> mClickArray = new SparseArray<>();
//存放长按点击事件
public SparseArray<View.OnLongClickListener> mLondClickArray = new SparseArray<>();
//存放对话框图标
public SparseArray<Integer> mIconArray = new SparseArray<>();
//存放对话框图片
public SparseArray<Bitmap> mBitmapArray = new SparseArray<>();
//对话框布局资源id
public int mLayoutResId;
//对话框的view
public View mView;
//对话框宽度
public int mWidth;
//对话框高度
public int mHeight;
//对话框垂直外边距
public int mHeightMargin;
//对话框横向外边距
public int mWidthMargin;
//动画
public int mAnimation;
//对话框显示位置
public int mGravity = Gravity.CENTER;
public AlertParams(Context context, int themeResId) {
mContext = context;
mThemeResId = themeResId;
}
public void apply(AlertController alert) {
//设置对话框布局
DialogViewHelper dialogViewHelper = null;
if (mLayoutResId != 0) {
dialogViewHelper = new DialogViewHelper(mContext, mLayoutResId);
}
if (mView != null) {
dialogViewHelper = new DialogViewHelper();
dialogViewHelper.setContentView(mView);
}
if (dialogViewHelper == null) {
throw new IllegalArgumentException("please set layout");
}
//将对话框布局设置到对话框
alert.getDialog().setContentView(dialogViewHelper.getContentView());
//设置DialogViewHelper辅助类
alert.setDialogViewHelper(dialogViewHelper);
//设置文本
for (int i = 0; i < mTextArray.size(); i++) {
alert.setText(mTextArray.keyAt(i), mTextArray.valueAt(i));
}
//设置图标
for (int i = 0; i < mIconArray.size(); i++) {
alert.setIcon(mIconArray.keyAt(i), mIconArray.valueAt(i));
}
//设置点击
for (int i = 0; i < mClickArray.size(); i++) {
alert.setOnClickListener(mClickArray.keyAt(i), mClickArray.valueAt(i));
}
//配置自定义效果,底部弹出,宽高,动画,全屏
Window window = alert.getWindow();
window.setGravity(mGravity);//显示位置
if (mAnimation != 0) {
window.setWindowAnimations(mAnimation);//设置动画
}
//设置宽高
WindowManager.LayoutParams params = window.getAttributes();
params.width = mWidth;
params.height = mHeight;
params.verticalMargin = mHeightMargin;
params.horizontalMargin = mWidthMargin;
window.setAttributes(params);
}
}
}
DialogViewHelper.java
package com.llw.mvplibrary.view.dialog;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import java.lang.ref.WeakReference;
public class DialogViewHelper {
private View mContentView;
private SparseArray<WeakReference<View>> mViews;
public DialogViewHelper(Context context, int layoutResId) {
this();
mContentView = LayoutInflater.from(context).inflate(layoutResId, null);
}
public DialogViewHelper() {
mViews = new SparseArray<>();
}
public <T extends View> void setText(int viewId, CharSequence text) {
TextView tv = getView(viewId);
if (tv != null) {
tv.setText(text);
}
}
public <T extends View> T getView(int viewId) {
WeakReference<View> weakReference = mViews.get(viewId);
View view = null;
if (weakReference != null) {
view = weakReference.get();
}
if (view == null) {
view = mContentView.findViewById(viewId);
if (view != null) {
mViews.put(viewId, new WeakReference<>(view));
}
}
return (T) view;
}
public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {
View view = getView(viewId);
if (view != null) {
view.setOnClickListener(onClickListener);
}
}
public void setIcon(int viewId, int resId) {
ImageView iv = getView(viewId);
if (iv != null) {
iv.setImageResource(resId);
}
}
public void setContentView(View contentView) {
mContentView = contentView;
}
public View getContentView() {
return mContentView;
}
}
OK,弹窗有了,布局还没有的。在app下的layout中新建一个dialog_update_app_tip.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/dp_270"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="@dimen/dp_270"
android:layout_height="wrap_content"
android:background="@mipmap/pic_update" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/dp_26">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_8"
android:text="发现新版本"
android:textColor="@color/black"
android:textSize="@dimen/sp_16" />
<TextView
android:id="@+id/tv_update_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_12"
android:paddingLeft="@dimen/dp_24"
android:paddingRight="@dimen/dp_24"
android:text="再也不会错过更多精彩啦!" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_50">
<TextView
android:id="@+id/tv_cancel"
android:layout_width="@dimen/dp_0"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/shape_left_bg"
android:foreground="@drawable/bg_white"
android:gravity="center"
android:text="下次再说"
android:textColor="#597EF7"
android:textSize="@dimen/sp_16" />
<TextView
android:id="@+id/tv_fast_update"
android:layout_width="@dimen/dp_0"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/shape_right_bg"
android:foreground="@drawable/bg_white"
android:gravity="center"
android:text="立即更新"
android:textColor="@color/white"
android:textSize="@dimen/sp_16" />
</LinearLayout>
</LinearLayout>
图片
shape_left_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:bottomLeftRadius="@dimen/dp_12"/>
<solid android:color="@color/gray_white"/>
</shape>
shape_right_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:bottomRightRadius="@dimen/dp_12"/>
<solid android:color="@color/blue"/>
</shape>
布局也有了,现在要来显示这个弹窗了。
在MainActivity中
private AlertDialog updateAppDialog = null;//应用更新提示弹窗
/**
* 应用更新提示弹窗
* @param downloadUrl 下载地址
* @param updateLog 更新日志
*/
private void showUpdateAppDialog(String downloadUrl,String updateLog) {
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.addDefaultAnimation()//默认弹窗动画
.setCancelable(true)
.setText(R.id.tv_update_info,updateLog)
.setContentView(R.layout.dialog_update_app_tip)//载入布局文件
.setWidthAndHeight(SizeUtils.dp2px(context, 270), ViewGroup.LayoutParams.WRAP_CONTENT)//设置弹窗宽高
.setOnClickListener(R.id.tv_cancel, v -> {//取消
updateAppDialog.dismiss();
}).setOnClickListener(R.id.tv_fast_update, v -> {//立即更新
//开始下载
});
updateAppDialog = builder.create();
updateAppDialog.show();
}
然后在检查的返回中调用
再在灾害预警的返回中调用这个checkVersion()方法
对了还有一个小问题,就是被和风给摆了一道,就是这个平台突然改了返回值,导致我拿数据时空对象了,然后程序崩溃了。然后我查了一下是搜索城市的返回数据发生了改变,也不知道为啥突然就改了一个参数的键,打开NewSearchCityResponse,将原来的statu替换成code,然后get和set方法也改一下。
然后在使用的地方一一修改就可以了
现在运行一下,不过你要改一下版本名,因为我平台上是2.2,所以我改成2.1,然后运行
看起来还是不错的呀。然后继续往下走,
5.下载应用、安装应用
在MainActivity中新增如下方法:
/**
* 清除APK
* @param apkName
* @return
*/
public static File clearApk(String apkName) {
File apkFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), apkName);
if (apkFile.exists()) {
apkFile.delete();
}
return apkFile;
}
/**
* 下载APK
* @param downloadUrl
*/
private void downloadApk(String downloadUrl) {
clearApk("GoodWeather.apk");
//下载管理器 获取系统下载服务
DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
//设置运行使用的网络类型,移动网络或者Wifi都可以
request.setAllowedNetworkTypes(request.NETWORK_MOBILE | request.NETWORK_WIFI);
//设置是否允许漫游
request.setAllowedOverRoaming(true);
//设置文件类型
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String mimeString = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(downloadUrl));
request.setMimeType(mimeString);
//设置下载时或者下载完成时,通知栏是否显示
request.setNotificationVisibility(request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("下载新版本");
request.setVisibleInDownloadsUi(true);//下载UI
//sdcard目录下的download文件夹
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "GoodWeather.apk");
//将下载请求放入队列
downloadManager.enqueue(request);
}
这里我调用了系统的DownloadManager进行下载,在通知栏进行,不过这个需要配置一个广播接收器,新建一个DownloadApkReceiver
package com.llw.goodweather.receiver;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import androidx.core.content.FileProvider;
import com.llw.goodweather.WeatherApplication;
import com.llw.goodweather.utils.Constant;
import com.llw.goodweather.utils.ToastUtils;
import java.io.File;
import java.util.Arrays;
/**
* 下载APK广播
*/
public class DownloadApkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {//下载完成
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
DownloadManager manager = (DownloadManager) WeatherApplication.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor cursor = manager.query(query);
if (!cursor.moveToFirst()) {
cursor.close();
return;
}
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {//成功
//安装
installApk(context);
}
}
}
/**
* 安装APK
* @param context
*/
public static void installApk(Context context) {
File file = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
, "GoodWeather.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if(Build.VERSION.SDK_INT>=24) { //判读版本是否在7.0以上
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
}else{
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
}
context.startActivity(intent);
}
}
然后就需要在AndroidManifest.xml中进行配置了,
在application标签中增加
<!--下载APK广播-->
<receiver
android:name=".receiver.DownloadApkReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
</intent-filter>
</receiver>
<!--Android7.0以后读取文件需要配置Provider-->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.llw.goodweather.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<!--元数据-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
这里还需要再添加一个权限,用于APP安装
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /><!--应用安装-->
注意一点
然后在xml下新建一个file_paths.xml文件
里面的包名还是用自己的
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path path="Android/data/com.llw.goodweather.fileprovider/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
然后在MainActivity中
来演示一下
然后再加一个提示Toast
OK,至此自动更新功能就写好了。
源码地址:GoodWeather
欢迎 Star 和 Fork