1. 程式人生 > >Android app實現自更新和安裝,許可權檢測適配Android6.0以下和Android6.0和Android7.0和Android8.0總結篇

Android app實現自更新和安裝,許可權檢測適配Android6.0以下和Android6.0和Android7.0和Android8.0總結篇

首先下載問檔案需要在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;
}
    }




}