效果图
开发流程
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,至此自动更新功能就写好了。