今天產品又有特大喜訊啦,App要添加新功能了普(ma)天(de)同(zhi)慶(zhang)~~~android
登錄頁面就強制用戶更新。。。git
腦袋疼+1api
寫吧緩存
首先是三個工具類app
apkide
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; } }
而後是佈局類ui
activity_update.xmlthis
<?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> <!--一、對應內部內存卡根目錄:Context.getFileDir()--> <!--<files-path--> <!--name="int_root"--> <!--path="/" />--> <!--二、對應應用默認緩存根目錄:Context.getCacheDir()--> <!--<cache-path--> <!--name="app_cache"--> <!--path="/" />--> <!--三、對應外部內存卡根目錄:Environment.getExternalStorageDirectory()--> <!--<external-path--> <!--name="ext_root"--> <!--path="pictures/" />--> <!--四、對應外部內存卡根目錄下的APP公共目錄:Context.getExternalFileDir(String)--> <!--<external-files-path--> <!--name="ext_pub"--> <!--path="/" />--> <!--五、對應外部內存卡根目錄下的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開啓更新,而採用非綁定式服務。這樣後臺運行也能夠繼續下載避免被殺死,防止下載中止。