今天产品又有特大喜讯啦,App要添加新功能了普(ma)天(de)同(zhi)庆(zhang)~~~
登陆页面就强制用户更新。。。
脑壳疼+1
写吧
首先是三个工具类
apk
public class InstallApk { Activity context; public InstallApk(Activity context) { this.context = context; } public void installApk(File apkFile) { Intent intent = new Intent(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { boolean b = context.getPackageManager().canRequestPackageInstalls(); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(context.getApplicationContext(), BuildConfig.APPLICATION_ID+".fileProvider", apkFile); intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); context.startActivity(intent); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(context.getApplicationContext(), BuildConfig.APPLICATION_ID+".fileProvider", apkFile); intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); context.startActivity(intent); } else { intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } } } }
头布局
public class DownFileHelper { private static final String TAG = DownFileHelper.class.getSimpleName(); Handler handler; Context mContext; NotificationManager mNotifyManager; Notification.Builder builder; private CommonProgressDialog mDialog; private RemoteViews contentView; public DownFileHelper(Context mContext, Handler handler) { this.handler = handler; this.mContext = mContext; } private void createNotification(final long total, final long current) { mNotifyManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); builder.setSmallIcon(R.mipmap.ic_launcher);//必须要设置这个属性,否则不显示 contentView = new RemoteViews(mContext.getPackageName(), R.layout.common_progress_dialog); contentView.setProgressBar(R.id.progress, (int) total, (int) current, true); builder.setOngoing(true);//设置左右滑动不能删除 Notification notification = builder.build(); notification.contentView = contentView; mNotifyManager.notify(R.layout.common_progress_dialog, notification);//发送通知 } /** * 下载最新版本的apk * * @param path apk下载地址 */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public void downFile(final String path) { mDialog = new CommonProgressDialog(mContext); mDialog.setMessage("正在下载"); mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mDialog.setMax(100); mDialog.setIndeterminate(true); mDialog.setCancelable(true); mDialog.show(); mNotifyManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); Bitmap btm = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.ic_launcher);//可以换成你的app的logo if (Build.VERSION.SDK_INT >= 26) {
//创建 通知通道 channelid和channelname是必须的(自己命名就好) @SuppressLint("WrongConstant") NotificationChannel channel = new NotificationChannel("1", "Channel1", NotificationManager.IMPORTANCE_LOW); // channel.enableLights(true);//是否在桌面icon右上角展示小红点 channel.setLightColor(Color.GREEN);//小红点颜色 channel.setShowBadge(true); //是否在久按桌面图标时显示此渠道的通知 channel.enableLights(false); channel.enableVibration(false); // channel.setVibrationPattern(new long[]{0}); channel.setSound(null, null);
mNotifyManager.createNotificationChannel(channel); builder = new Notification.Builder(mContext, "1"); //设置通知显示图标、文字等 builder.setSmallIcon(R.mipmap.ic_launcher)//可以换成你的app的logo .setLargeIcon(btm) .setTicker("正在下载") .setContentTitle("我的app") .setAutoCancel(true) .build(); mNotifyManager.notify(1, builder.build()); } else { builder = new Notification.Builder(mContext); builder.setSmallIcon(R.drawable.ic_danmuku_off)//可以换成你的app的logo .setLargeIcon(btm) .setTicker("正在下载") .setContentTitle("我的app") .setAutoCancel(true)//可以滑动删除通知栏 .build(); mNotifyManager.notify(1, builder.build()); } new Thread() { public void run() { try { URL url = new URL(path); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setReadTimeout(5000); con.setConnectTimeout(5000); con.setRequestProperty("Charset", "UTF-8"); con.setRequestMethod("GET"); if (con.getResponseCode() == 200) { int length = con.getContentLength();// 获取文件大小 InputStream is = con.getInputStream(); FileOutputStream fileOutputStream = null; if (is != null) { //对apk进行保存 File file = new File(Environment.getExternalStorageDirectory() .getPath(), "your_app_name.apk"); fileOutputStream = new FileOutputStream(file); byte[] buf = new byte[1024]; int ch; int process = 0; NumberFormat numberFormat = NumberFormat.getInstance(); // 设置精确到小数点后2位 numberFormat.setMaximumFractionDigits(2); String result; while ((ch = is.read(buf)) != -1) { fileOutputStream.write(buf, 0, ch); process += ch; //更新进度条 result = numberFormat.format((float) process / (float) length * 100); mDialog.setProgress((int) ((float) process / (float) length * 100)); builder.setContentText("下载进度:" + result + "%"); builder.setProgress(length, process, false); mNotifyManager.notify(1, builder.build()); } } if (fileOutputStream != null) { fileOutputStream.flush(); fileOutputStream.close(); } //apk下载完成,使用Handler()通知安装apk builder.setProgress(length, length, false); builder.setContentText("已经下载完成"); mNotifyManager.notify(1, builder.build()); mNotifyManager.cancelAll(); handler.sendEmptyMessage(0); } else { Log.e(TAG, "run: ResponseCode"+ con.getResponseCode()); } } catch (Exception e) { e.printStackTrace(); } } }.start(); } }
Dialog
public class CommonProgressDialog extends AlertDialog { private ProgressBar mProgress; private TextView mProgressNumber; private TextView mProgressPercent; private TextView mProgressMessage; private Handler mViewUpdateHandler; private int mMax; private CharSequence mMessage; private boolean mHasStarted; private int mProgressVal; private String TAG = "CommonProgressDialog"; private String mProgressNumberFormat; private NumberFormat mProgressPercentFormat; private TextView tvCancel; public CommonProgressDialog(Context context) { super(context); // TODO Auto-generated constructor stub initFormats(); } @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.common_progress_dialog); mProgress = (ProgressBar) findViewById(R.id.progress); mProgressNumber = (TextView) findViewById(R.id.progress_number); mProgressPercent = (TextView) findViewById(R.id.progress_percent); mProgressMessage = (TextView) findViewById(R.id.progress_message); // LayoutInflater inflater = LayoutInflater.from(getContext()); mViewUpdateHandler = new Handler() { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); int progress = mProgress.getProgress(); int max = mProgress.getMax(); double dProgress = (double)progress; double dMax = (double)max; if (mProgressNumberFormat != null) { String format = mProgressNumberFormat; mProgressNumber.setText(String.format(format, dProgress, dMax)); } else { mProgressNumber.setText(""); } if (mProgressPercentFormat != null) { double percent = (double) progress / (double) max; SpannableString tmp = new SpannableString(mProgressPercentFormat.format(percent)); tmp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mProgressPercent.setText(tmp); } else { mProgressPercent.setText(""); } } }; onProgressChanged(); if (mMessage != null) { setMessage(mMessage); } if (mMax > 0) { setMax(mMax); } if (mProgressVal > 0) { setProgress(mProgressVal); } } private void initFormats() { mProgressNumberFormat = "%s/%s"; mProgressPercentFormat = NumberFormat.getPercentInstance(); mProgressPercentFormat.setMaximumFractionDigits(0); } private void onProgressChanged() { mViewUpdateHandler.sendEmptyMessage(0); } public void setProgressStyle(int style) { //mProgressStyle = style; } public int getMax() { if (mProgress != null) { return mProgress.getMax(); } return mMax; } public void setMax(int max) { if (mProgress != null) { mProgress.setMax(max); onProgressChanged(); } else { mMax = max; } } public void setIndeterminate(boolean indeterminate) { if (mProgress != null) { mProgress.setIndeterminate(indeterminate); } } public void setProgress(int value) { if (mHasStarted) { mProgress.setProgress(value); onProgressChanged(); } else { mProgressVal = value; } } @Override public void setMessage(CharSequence message) { // TODO Auto-generated method stub if(mProgressMessage!=null){ mProgressMessage.setText(message); } else{ mMessage = message; } } @Override protected void onStart() { // TODO Auto-generated method stub super.onStart(); mHasStarted = true; } @Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); mHasStarted = false; } }
然后是布局类
activity_update.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:gravity="center" android:id="@+id/btn_install" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="安装" /> </android.support.constraint.ConstraintLayout>
common_progress_dialog.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <!--@drawable/common_progress_dialog_background"--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="#ffffff"> <!--Title--> <TextView android:id="@+id/progress_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:layout_marginTop="30dp" android:layout_gravity="center_horizontal" android:textColor="#000" android:text="234" /> <!--进度--> <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="10dp" android:layout_marginTop="10dp" android:layout_marginLeft="30dp" android:layout_marginRight="30dp" android:layout_centerHorizontal="true" android:progressDrawable="@drawable/seekbar_style" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="30dp" android:layout_marginRight="30dp" > <!--左边的--> <TextView android:id="@+id/progress_percent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:textSize="18sp" android:gravity="center_horizontal" android:textColor="#000" android:text="3243" /> <!--右边的--> <TextView android:id="@+id/progress_number" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="30dp" android:textSize="16sp" android:gravity="center_horizontal" android:textColor="#000" android:text="23424sdfsf2" android:layout_alignParentRight="true" /> </RelativeLayout> </LinearLayout> </FrameLayout>
drawable文件夹资源文件seekbar_style.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@android:id/background"> <shape > <corners android:radius="10dip" /> <gradient android:angle="270" android:centerColor="#151515" android:centerY="0.45" android:endColor="#151515" android:startColor="#151515" /> </shape> </item> <item android:id="@android:id/secondaryProgress"> <clip > <shape > <corners android:radius="10dip" /> <gradient android:angle="270" android:centerColor="#333333" android:centerY="0.45" android:endColor="#333333" android:startColor="#333333" /> </shape> </clip> </item> <item android:id="@android:id/progress" > <clip > <shape > <corners android:radius="10dip" /> <gradient android:angle="270" android:centerColor="#a07e5d" android:centerY="0.45" android:endColor="#a07e5d" android:startColor="#a07e5d" /> </shape> </clip> </item> </layer-list>
res内新建一个xml文件夹内设置file_provider.xml
<?xml version="1.0" encoding="utf-8"?> <paths> <!--1、对应内部内存卡根目录:Context.getFileDir()--> <!--<files-path--> <!--name="int_root"--> <!--path="/" />--> <!--2、对应应用默认缓存根目录:Context.getCacheDir()--> <!--<cache-path--> <!--name="app_cache"--> <!--path="/" />--> <!--3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()--> <!--<external-path--> <!--name="ext_root"--> <!--path="pictures/" />--> <!--4、对应外部内存卡根目录下的APP公共目录:Context.getExternalFileDir(String)--> <!--<external-files-path--> <!--name="ext_pub"--> <!--path="/" />--> <!--5、对应外部内存卡根目录下的APP缓存目录:Context.getExternalCacheDir()--> <!--<external-cache-path--> <!--name="ext_cache"--> <!--path="/" />--> <external-path path="Android/com.guorentong.learn.myapplication/" name="files_root" /> <external-path path="." name="external_storage_root" /> </paths>
注意path="Android/com.guorentong.learn.myapplication/"要和自己的报名一致
然后清单文件里application标签内设置
<!-- FileProvider配置访问路径,适配7.0及其以上 --> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.guorentong.learn.myapplication.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider" /> </provider>
在Activity中设置
//版本更新 private String[] permissionStr = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: new InstallApk(HomeActivity.this) .installApk(new File(Environment.getExternalStorageDirectory(), "your_app_name.apk")); break; } } }; @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public void permissionsCheckAndDownload() { if (Build.VERSION.SDK_INT >= 23) { applyPermission(); } else { new DownFileHelper(HomeActivity.this, handler) .downFile(mUrl); } } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) private void applyPermission() { /** * 第 1 步: 检查是否有相应的权限 */ boolean isAllGranted = checkPermissionAllGranted(permissionStr); // 如果这3个权限全都拥有, 则直接执行备份代码 if (!isAllGranted) { /** * 第 2 步: 请求权限 */ // 一次请求多个权限, 如果其他有权限是已经授予的将会自动忽略掉 ActivityCompat.requestPermissions(this, permissionStr,1); } else { new DownFileHelper(HomeActivity.this, handler) .downFile(mUrl); } } /** * 检查是否拥有指定的所有权限 */ private boolean checkPermissionAllGranted(String[] permissions) { for (String permission : permissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { // 只要有一个权限没有被授予, 则直接返回 false return false; } } return true; } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); boolean isAllGranted = true; // 判断是否所有的权限都已经授予了 for (int grant : grantResults) { if (grant != PackageManager.PERMISSION_GRANTED) { isAllGranted = false; break; } } if (isAllGranted) { // 如果所有的权限都授予了, 则执行备份代码 new DownFileHelper(HomeActivity.this, handler) .downFile(mUrl); } else { // 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮 openAppDetails(); } } private void openAppDetails() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("备份通讯录需要访问 “通讯录” 和 “外部存储器”,请到 “应用信息 -> 权限” 中授予!"); builder.setPositiveButton("去手动授权", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("package:" + getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); } }); builder.setNegativeButton("取消", null); builder.show(); }
使用的时候判断版本,或者返回字段调用方法
permissionsCheckAndDownload();
就可以了
别忘了设置权限允许未知程序安装
//安装权限 <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
如果需要改变样式可以自己修改
最好 其实 是不用activity开启更新,而采用非绑定式服务。这样后台运行也可以继续下载避免被杀死,防止下载停止。