Android app實現自更新和安裝,許可權檢測適配Android6.0以下和Android6.0和Android7.0和Android8.0總結篇
阿新 • • 發佈:2019-01-10
首先下載問檔案需要在AndroidManifest.xml裡新增SD卡讀寫許可權,下面兩個許可權:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
下載app還需要一個下載幫助類,檔案的讀寫流類;此類外界呼叫時需要傳人Handler,主要負責app下載完成時通知外界做安裝操作,此外還添加了安卓的系統通知欄Notification,負責在系統通知欄上顯示下在進度,由於Android對通知欄做了重構,Notification只能安卓16以上的api呼叫,16以下用NotificationCompat,如果下相容api16以下自行查閱NotificationCompat使用新增相容,此外Android8.0對Notification的呼叫還與8.0以下的不一樣!(如下看程式碼!)如果有同學不需要系統通知欄顯示進度可自行遮蔽Notification的程式碼。也可以刪了Notification的程式碼 自己寫一個Dialog進度條去顯示進度!
import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.os.Build; import android.os.Environment;import android.os.Handler; import android.util.Log; import com.qianjinjia.zhishan.R; import com.qianjinjia.zhishan.dialog.ProgressBarDialog; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.text.NumberFormat;/** * Created by Administrator on 2018/3/9. */ public class DownFileHelper { Handler handler; Context mContext;NotificationManager mNotifyManager; Notification.Builder builder; public DownFileHelper(Context mContext, Handler handler) { this.handler = handler; this.mContext = mContext; } /** * 下載最新版本的apk * * @param path apk下載地址 */ public void downFile(final String path) { mNotifyManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); Bitmap btm = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.app_icon);//可以換成你的app的logo if (Build.VERSION.SDK_INT >= 26) { //建立 通知通道 channelid和channelname是必須的(自己命名就好) NotificationChannel channel = new NotificationChannel("1", "Channel1", NotificationManager.IMPORTANCE_DEFAULT); channel.enableLights(true);//是否在桌面icon右上角展示小紅點 channel.setLightColor(Color.GREEN);//小紅點顏色 channel.setShowBadge(true); //是否在久按桌面圖示時顯示此渠道的通知 mNotifyManager.createNotificationChannel(channel); builder = new Notification.Builder(mContext, "1"); //設定通知顯示圖示、文字等 builder.setSmallIcon(R.mipmap.app_logo)//可以換成你的app的logo .setLargeIcon(btm) .setTicker("正在下載") .setContentTitle("我的app") .setAutoCancel(true) .build(); mNotifyManager.notify(1, builder.build()); } else { builder = new Notification.Builder(mContext); builder.setSmallIcon(R.mipmap.app_logo)//可以換成你的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); 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); } } catch (Exception e) { e.printStackTrace(); } } }.start(); } }
安裝app的實現類,其中Android8.0安裝時需要檢測和申請app可安裝未知來源許可權允許,在AndroidManifest.xml新增
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>還有Android7.0之後檔案共享需要使用FileProvider的功能,uri
package com.qianjinjia.zhishan.helper; import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.support.v4.app.ActivityCompat; import android.support.v4.content.FileProvider; import com.qianjinjia.zhishan.BuildConfig; import com.qianjinjia.zhishan.dialog.ConfirmDialog; import java.io.File; import java.io.IOException; /** * Created by Administrator on 2018/3/13. */ 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(); if (b) { 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 { //請求安裝未知應用來源的許可權 ConfirmDialog confirmDialog =new ConfirmDialog(context); confirmDialog.setStyle("安裝許可權","Android8.0安裝應用需要開啟未\n知來源許可權,請去設定中開啟許可權", "去設定","取消"); confirmDialog.setClicklistener(new ConfirmDialog.ClickListenerInterface() { @Override public void doConfirm() { String[] mPermissionList = new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}; ActivityCompat.requestPermissions(context, mPermissionList, 2); } }); confirmDialog.show(); } } 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); } } } }
還需要個許可權檢測和許可權申請的幫助類
package com.qianjinjia.zhishan.helper; import android.app.Activity; import android.content.pm.PackageManager; import android.os.Build; import android.support.annotation.RequiresApi; import android.support.v4.app.ActivityCompat; /** * Created by Administrator on 2018/3/14. */ //許可權檢測和申請幫助類 public class PermissionHelper { Activity activity; public PermissionHelper(Activity activity) { this.activity = activity; } /** * 第 1 步: 檢查是否擁有指定的所有許可權 */ public boolean checkPermissionAllGranted(String[] permissions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { for (String permission : permissions) { if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) { // 只要有一個許可權沒有被授予, 則直接返回 false return false; } } return true; } return true; } /** * 第 2 步: 請求許可權 */ // 一次請求多個許可權, 如果其他有許可權是已經授予的將會自動忽略掉 public void requestPermissionAllGranted(String[] permissions, int i) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ActivityCompat.requestPermissions(activity, permissions, i); } } }
然後在Activity裡負責是否更新邏輯和下載所需要的許可權申請和檢測以及安裝app的操作
public class MainActivity extends BaseActivity<MainPresenter> implements MainContract.View { Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: new InstallApk(MainActivity.this) .installApk(new File(Environment.getExternalStorageDirectory(), "your_app_name.apk")); break; } } }; @Override protected int getLayout() { return R.layout.activity_main; } //這裡是請求服務的是否更新介面返回資料的處理,你可以結合你自己後端的處理 @Override public void updateonSuccess(UpdateBean bean) { if (Integer.valueOf(bean.getData().getAndroid_version_index()) > SystemTool.getAppVersionCode(MyApplication._context)) { url = bean.getData().getAndroid_download_url(); if (bean.getData().getAndroid_is_update() == 2) { //更新版本 String today = DateUtils.getNowTime(); String days = (String) SpUtils.getParam(getApplicationContext(), "day", ""); if (!days.equals(today)) { updateDialog(bean); } } else { //強制更新版本 forcedUpdateDialog(bean); } } } private void forcedUpdateDialog(UpdateBean bean) { aButtonDialog.setStyle("版本更新", bean.getData().getUpdate_desc(), "立即更新"); aButtonDialog.setClicklistener(new AButtonDialog.ClickListenerInterface() { @Override public void doConfirm() { permissionsCheckAndDownload(); } }); aButtonDialog.setOnKeyListener(new DialogInterface.OnKeyListener() { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { aButtonDialog.dismiss(); android.os.Process.killProcess(android.os.Process.myPid()); //獲取PID System.exit(0); //常規java、c#的標準退出法,返回值為0代表正常退出 } return false; } }); aButtonDialog.show(); } private void updateDialog(UpdateBean bean) { confirmDialog.setStyle("版本更新", bean.getData().getUpdate_desc(), "立即更新", "取消"); confirmDialog.setClicklistener(new ConfirmDialog.ClickListenerInterface() { @Override public void doConfirm() { permissionsCheckAndDownload(); } }); confirmDialog.setOnCancelClickListener(new ConfirmDialog.OnCancelClickListener() { @Override public void onCancelClick() { String today = DateUtils.getNowTime(); SpUtils.put(getApplicationContext(), "day", today); } }); confirmDialog.show(); } private void permissionsCheckAndDownload() { if (Build.VERSION.SDK_INT >= 23) { permissionsCheck(); } else { new DownFileHelper(MainActivity.this, handler) .downFile(url); } } private void permissionsCheck() { if (!permissionHelper.checkPermissionAllGranted(mPermissionList)) { permissionHelper.requestPermissionAllGranted(mPermissionList, 1); } else { new DownFileHelper(MainActivity.this, handler) .downFile(url); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new DownFileHelper(MainActivity.this, handler) .downFile(url); } else { //不給讀寫許可權處理 } break; case 2: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new InstallApk(MainActivity.this) .installApk(new File(Environment.getExternalStorageDirectory(), "your_app_name.apk")); } else { Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); startActivityForResult(intent, 10012); } break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case 10012: Log.d("resultCode", resultCode + ""); if (Build.VERSION.SDK_INT >= 26) { boolean b = getPackageManager().canRequestPackageInstalls(); if (b) { new InstallApk(MainActivity.this) .installApk(new File(Environment.getExternalStorageDirectory(), "qianjinjia.apk")); } else { final AButtonDialog aButton = new AButtonDialog(MainActivity.this); aButton.setStyle("您未開啟未知來源\n許可權不能及時更新", "知道了"); aButton.setClicklistener(new AButtonDialog.ClickListenerInterface() { @Override public void doConfirm() { } }); aButton.setOnKeyListener(new DialogInterface.OnKeyListener() { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { aButton.dismiss(); android.os.Process.killProcess(android.os.Process.myPid()); //獲取PID System.exit(0); //常規java、c#的標準退出法,返回值為0代表正常退出 } return false; } }); aButton.show(); } } break; default: break; } } }