Android App-targetSDKVersion28升级为30

news/2024/7/2 23:23:59

为什么要修改targetSDKVersion?

1、应用开发平台要求(小米)
2、更好的兼容新版本的手机

有targetSDKVersion的位置:

  • App的targetSDKVersion
  • Module中的targetSDKVersion
  • 引入的第三方库中有targetSDKVersion

修改了App和Module中的targetSDKVersion。

存储权限和存储位置问题

修改原因:
Android10(targetSdkVersion29)以后加入了分区存储,针对App设置了外置私有存储空间,卸载App时会被删除,一定程度上解决了卸载App后外置内存仍占用问题。

Android10在获取存储权限上和之前版本有区别。

修改前:

  • targetSDKVersion28
  • 使用EasyPermission框架获取权限。(长时间不更新且不支持新版本Android)
  • 视频、音频、文件存储在外置存储卡【Environment.getExternalStorageDirectory()】中。

修改后:

  • targetSDKVersion30(Android 11)
  • 使用Permission替换EasyPermission获取权限
  • 视频、音频、文件存储存储在私有外置存储中【context.getExternalFilesDir()】。

修改内容:

  • 兼容Android不同版本获取权限。
  • App新版本要处理老版本存储在外置存储卡中的内容。

知识点:
使用创建、读写文件测试对Environment.getExternalStorageDirectory()和context.getExternalFilesDir()目录的使用。

Environment.getExternalStorageDirectory()

  • Android13:同Android11
  • Android11:
    • 动态获取Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE权限后创建读写文件时显示permission denied。
    • 动态获取Manifest.permission.MANAGE_EXTERNAL_STORAGE权限后可以创建并读写文件,获取权限方式是通过Intent intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)跳转至设置页面,用户自行打开允许访问所有文件权限看后才可创建读写文件。
  • Android10:同Android11
  • Android9 动态获取Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE权限后可以创建并读写文件。
  • Android6:动态获取Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE权限后可以创建并读写文件。
  • Android5:不用获取权限可以创建并读写文件。

context.getExternalFilesDir();

  • Android13:不用获取权限可以创建并读写文件。
  • Android11:不用获取权限可以创建并读写文件。
  • Android10:不用获取权限可以创建并读写文件。
  • Android9:不用获取权限可以创建并读写文件。
  • Android6:不用获取权限可以创建并读写文件。
  • Android5:不用获取权限可以创建并读写文件。

修改方案:

  • 修改视频、音频和文件存储位置为外置私有存储空间且不需要获取权限。
  • 为兼容App旧版本存储在外置存储卡中的视频、音频和文件,在读取外置存储卡中的视频、音频和文件时需获取存储权限。

读取相册照片和视频

修改原因:

  • Android10之后读取相册的权限不同且方法也不同。

修改前:

  • 使用RxGalleryFinal框架(长时间不更新且不适配新版本Android)

修改后:

  • 使用PictureSelector框架(适配Android10以后版本-内置有获取权限功能)

保存图片至相册

修改原因:

  • Android10 添加至相册方式和位置不同。

修改后:

  • 兼容不同Android版本

处理方法:

  • Android9及之前版本,将图片保存至外置存储卡,然后通知将其添加到相册数据库,并刷新即可(需存储权限)。
  • Android10及以后版本,不能将图片放置在私有外置存储空间(ContentResolver不支持,会提示错误),将图片放在公共媒体存放位置,然后刷新,使用ContentResolver实现。

实现代码如下:

package com.ypkj.kfpt.http;


import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.widget.Toast;


import androidx.fragment.app.FragmentActivity;


import com.learn.base.permission.PermissionsUtil;
import com.ypkj.kfpt.R;


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.UUID;


/**
 * 下载图片至相册的工具类
 */


public class DonwloadSaveImg {
    private static Context context;
    private static String filePath;
    private static Bitmap mBitmap;
    private static String mSaveMessage = "失败";
    private final static String TAG = "PictureActivity";


    public static void donwloadImg( Context contexts, String filePaths) {
        context = contexts;
        filePath = filePaths;
        PermissionsUtil.getInstance().requestStoragePerm((FragmentActivity) contexts, "", new PermissionsUtil.BaseRequestCallback() {
            @Override
            public void onCallBack() {
                new Thread(saveFileRunnable).start();
            }
        });
    }


    private static Runnable saveFileRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                if (!TextUtils.isEmpty(filePath)) { //网络图片
                    // 对资源链接
                    URL url = new URL(filePath);
                    //打开输入流
                    InputStream inputStream = url.openStream();
                    //对网上资源进行下载转换位图图片
                    mBitmap = BitmapFactory.decodeStream(inputStream);
                    inputStream.close();
                }
                saveBitmap(context, mBitmap);
                mSaveMessage = "图片保存成功!";
            } catch (IOException e) {
                mSaveMessage = "图片保存失败!";
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
            messageHandler.sendMessage(messageHandler.obtainMessage());
        }
    };


    private static Handler messageHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {


            Log.d(TAG, mSaveMessage);
            Toast.makeText(context, mSaveMessage, Toast.LENGTH_SHORT).show();
        }
    };




    // 保存bitmap到相册,并兼容AndroidQ
    public static boolean saveBitmap(Context context, Bitmap bitmap) {
        if (bitmap == null) {
            return false;
        }
        boolean isSaveSuccess;
        if (Build.VERSION.SDK_INT < 29) {
            isSaveSuccess = saveImageToGallery(context, bitmap);
        } else {
            isSaveSuccess = saveImageToGalleryQ(context, bitmap);
        }
        return isSaveSuccess;
    }


    /**
     * android 10 以下版本
     */
    private static boolean saveImageToGallery(Context context, Bitmap image) {
        // 首先保存图片
        String storePath = getBitmapFileDir();


        File appDir = new File(storePath);
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        String fileName = UUID.randomUUID().toString() + ".png";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            // 通过io流的方式来压缩保存图片
            boolean isSuccess = image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.flush();
            fos.close();


            // 保存图片后发送广播通知更新数据库
            Uri uri = Uri.fromFile(file);
            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
            if (isSuccess) {
                return true;
            } else {
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * android 10 以上版本
     */
    private static boolean saveImageToGalleryQ(Context context, Bitmap image) {
        long mImageTime = System.currentTimeMillis();
        String mImageFileName = UUID.randomUUID().toString() + ".png";
        final ContentValues values = new ContentValues();
        values.put(MediaStore.MediaColumns.RELATIVE_PATH, getBitmapFileDir());
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName);
        values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
        values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000);
        values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000);
        values.put(MediaStore.MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
        values.put(MediaStore.MediaColumns.IS_PENDING, 1);


        ContentResolver resolver = context.getContentResolver();
        final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        try {
            OutputStream out = resolver.openOutputStream(uri);
            if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
                return false;
            }
            values.clear();
            values.put(MediaStore.MediaColumns.IS_PENDING, 0);
            values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
            resolver.update(uri, values, null, null);
        } catch (Exception e) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                resolver.delete(uri, null);
            }
            return false;
        }
        return true;
    }


    public static String getBitmapFileDir() {
        String BITMAP_FILE_DIRECTORY = context.getString(R.string.appname);
        if (Build.VERSION.SDK_INT < 29) {
            // android 10 以下版本
            return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + BITMAP_FILE_DIRECTORY;
        } else {
            return Environment.DIRECTORY_PICTURES + File.separator + BITMAP_FILE_DIRECTORY;
        }
    }


}

参考文档:

  • 一篇文章搞定《Android权限问题(全版本)》https://blog.csdn.net/weixin_45112340/article/details/128905213
  • [ ]应用TargetSdkVersion 30升级适配指南 https://dev.mi.com/distribute/doc/details?pId=1738#_2

http://lihuaxi.xjx100.cn/news/1883038.html

相关文章

2.2 C语言之常量

2.2 C语言之常量 一、常量 一、常量 类似于1234的整数常量属于int类型。 printf("%d\n", 1234);long类型的常量以l或L结尾 printf("%d\n", 123456789l);printf("%d\n", 123456789L);如果一个整数太大&#xff0c;以至于无法用int类型表示时&…

D28|买卖股票的最佳时机+跳跃游戏

122.买卖股票的最佳时机 II 初始思路&#xff1a; 这道题解题的时候比较像在找规律&#xff0c;发现只要计算这个过程中的两数之差然后相加即可。 题解复盘&#xff1a; 可以更加清晰的发现如何从题意中获得贪心的思路。 如何贪心&#xff1a;局部最优&#xff1a;收集每天的…

PPP协议概述与实验示例

PPP协议概述与实验示例 概述 PPP&#xff08;Point-to-Point Protocol&#xff09;是一种用于在点对点连接上传输多协议数据包的标准方法。它已经成为最广泛使用的互联网接入数据链路层协议&#xff0c;可以与各种技术结合&#xff0c;如ADSL、LAN等&#xff0c;实现宽带接入…

TCP 和 UDP 区别? 2、TCP/IP 协议涉及哪几层架构? 3、描述下 TCP 连接 4 次挥手的过程?为什么要 4 次挥手?

文章目录 1、TCP 和 UDP 区别&#xff1f;2、TCP/IP 协议涉及哪几层架构&#xff1f;3、描述下 TCP 连接 4 次挥手的过程&#xff1f;为什么要 4 次挥手&#xff1f;4、计算机插上电源操作系统做了什么&#xff1f;5、Linux 操作系统设备文件有哪些&#xff1f; 1、TCP 和 UDP …

选项式API和组合式API

简介 Vue 3支持选项式API和组合式API。其中&#xff0c;选项式API是从Vue 2开始使用的一种写法&#xff0c;而Vue 3新增了组合式API的写法。 选项式API 选项式API是一种通过包含多个选项的对象来描述组件逻辑的API&#xff0c;其常用的选项包括data、methods、computed、watch…

人工智能在安全领域的应用

ChatGPT 等 AI 应用在网络安全领域的应用效果明显&#xff0c;其自动编程能力、分析能力及自身集成的知识库能够帮助网络安全从业者提升工作效率&#xff0c;改进组织的网络安全计划。 &#xff08;一&#xff09;代码生成与检测能力 可用于开发漏洞挖掘工具。如目前可以利用…

事业编《综应 综合知识应用能力 综合应用》笔记

文章目录&#xff1a; 一&#xff1a;归纳概况题 二&#xff1a;对策措施题 三&#xff1a;综合分析题 四&#xff1a;公文写作题 五&#xff1a;事务处理题 六&#xff1a;案例分析题 1.问题类 2.原因类 3.影响类 4.场景方案类 七&#xff1a;材料作文题 八&#…

麒麟系统10s无法登录问题分析报告

概述 10S 4.0.2-sp4反馈系统无法登录。提示密码错误 分析 怀疑用户密码错误&#xff0c;添加consoletty0&#xff0c;init/bin/bash进单用户修改密码后 故障依旧 排除密码错误怀疑密码文件权限问题导致的密码未修改成功 查看/etc/shadow 相关文件是否进行修改 未见明显异常…