Android app实现自更新和安装,权限检测适配Android6.0以下和Android6.0和Android7.0和Android8.0,9.0以及10.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的获取不一样。具体看我的这篇博客 Android 7.0自动安装实现

如果你的目标版本是targetSdkVersion 29(目标版本不是29可忽略)

在Android10.0 google更改了文件读取和存储方式为了使用户能够更好地控制自己的文件,并限制文件混乱,AndroidQ修改了外部存储权限。这种外部存储的新特性被称为分区存储(Scoped Storage)有兴趣自己去了解下。

这里我了绕过分区存储因为Android Q规定了APP有两种外部存储空间视图模式:Legacy View、Filtered View。

  • Legacy View 兼容模式。与AndroidQ之前一样,申请权限后App可访问外部存储,拥有完整的访问权限,可以使用File的方式访问文件。
  • Filtered View 分区存储。APP只能直接访问App-specific目录,访问公共目录或者其他APP的App-specific目录,只能通过MediaStore、SAF、或者其他APP提供的ContentProvider、FileProvider等方式访问。

在AndroidQ上,target SDK大于或等于29的APP默认被赋予Filtered View。APP可以在AndroidManifest.xml中设置

requestLegacyExternalStorage来修改外部存储空间视图模式,true为Legacy View,false为Filtered View。

//在application中添加 默认是false,也就是Filtered View
android:requestLegacyExternalStorage="true"

可以通过Environment.isExternalStorageLegacy()方法判断运行模式。

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);

                        Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + context.getPackageName()));
                        context.startActivityForResult(intent, 10012);

                    }
                });
                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;
        }
    }



  

}

 

 

 

 

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值