精华内容
下载资源
问答
  • 关于Android 10.0适配,看这篇就够了

    万次阅读 多人点赞 2019-07-09 10:48:47
    本文将从三个角度介绍Android Q的部分适配问题,也是大家开发适配过程中大概率会遇到的问题: Q 行为变更:所有应用 (不管targetSdk是多少,对所有跑在Q设备上的应用均有影响) Q 行为变更:以 Android Q 为目标...

    本文将从三个角度介绍Android Q的部分适配问题,也是大家开发适配过程中大概率会遇到的问题:

    • Q 行为变更:所有应用 (不管targetSdk是多少,对所有跑在Q设备上的应用均有影响)
    • Q 行为变更:以 Android Q 为目标平台的应用(targetSDK == Q 才有影响)
    • 项目升级遇到的问题

    至于Q的新功能及SDK,项目中并没有涉及,故暂不介绍,只放出链接AndroidQ新API及功能。

    https://developer.android.com/preview/features

    Q行为变更:所有应用

    用户隐私权限变更

    AndroidQ引入了大量更改和限制以增强对用户隐私的保护。

    官方文档将这一部分内容独立于Q 行为变更:所有应用来介绍,是因为这一部分内容庞大且重要,个人认为Q的最大更新就是用户隐私权限变更。具体变更的权限如下:

    权限;   受影响应用;   如何启用(影响范围)

     

     

     

    存储权限

    访问和共享外部存储设备中的文件的应用

    adb shell sm set-isolated-storage on(下文详述)

    定位权限

    在后台时请求访问用户位置信息的应用

    这种权限策略在 Android Q 上始终处于启用状态

    从后台启动 Activity

    不需要用户互动就启动 Activity 的应用

    关闭允许系统执行后台活动开发者选项即可启用限制

    设备标识符(deviceId)

    访问设备序列号或 IMEI 的应用

    在搭载 Android Q 的设备上安装应用

    无线扫描权限

    使用 WLAN API 和 Bluetooth API 的应用

    以 Android Q 为目标平台

     

     

     因为从后台启动Activity权限和无线扫描权限两种权限的变更影响较少。本文不作详述,如有涉及请查阅官方文档。

    https://developer.android.com/preview/privacy/background-activity-starts

    从后台启动Activity权限变更仅针对与用户毫无交互就启动一个Activity的情况,(比如微信登陆授权)

    以下会着重介绍存储权限,定位权限和设备标识符三种权限的变更与适配。

    存储权限

    Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。

    沙盒,简单而言就是应用专属文件夹,并且访问这个文件夹无需权限。谷歌官方推荐应用在沙盒内存储文件的地址为Context.getExternalFilesDir()下的文件夹。比如要存储一张图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。

    以下将按访问的目标文件的地址介绍如何适配。

    1. 访问自己文件:Q中用更精细的媒体特定权限替换并取消了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己沙盒中的文件。

    2. 访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申请方法同原来的存储权限。

    3. 访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。

    4. 访问其他应用沙盒文件:如果你的应用需要使用其他应用在沙盒内创建的文件,请点击使用其他应用的文件,本文不做介绍。

    所以请判断当应用运行在Q平台上时,取消对READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE两个权限的申请。并替换为新的媒体特定权限。

    关于存储权限的(如何启用)影响范围

    模拟器

    在Android Q Beat1中,谷歌暂未开放存储权限的改动。我们需要使用adb命令

    adb shell sm set-isolated-storage on

    来开启模拟器对于存储权限的变更来进行适配。

    真机

    当满足以下每个条件时,将开启兼容模式,即不开启Q设备中的存储权限改动:

    1. 应用targetSDK<=P。
    2. 应用安装在从 Android P 升级到 Android Q 的设备上。

    但是当应用重新安装(更新)时,不会重新开启兼容模式,存储权限改动将生效。

    所以按官方文档所说,无论targetSDK是否为Q,必须对应用进行存储权限改动的适配。

    在我的测试中,当targetSDK<=P,在Q Beat1版上申请两个旧权限时会自动改成申请三个新权限,不会影响应用正常使用,但当targetSDK==Q时,申请旧权限将失败并影响应用正常使用。

    定位权限

    为了让用户更好地控制应用对位置信息的访问权限,Android Q 引入了新的位置权限 ACCESS_BACKGROUND_LOCATION。

    与现有的 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。除非应用的某个 Activity 可见或应用正在运行前台服务,否则应用将被视为在后台运行。

    与iOS系统一样,Q中也加入了后台位置权限ACCESS_BACKGROUND_LOCATION,如果应用需要在后台时也获得用户位置(比如滴滴),就需要动态申请ACCESS_BACKGROUND_LOCATION权限。

    当然如果不需要的话,应用就无需任何改动,且谷歌会按照应用的targetSDK作出不同处理:

    targetSDK <= P 应用如果请求了ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限,Q设备会自动帮你申请ACCESS_BACKGROUND_LOCATION权限。

    设备唯一标识符

    从 Android Q 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 签名权限才能访问设备的不可重置标识符(包含 IMEI 和序列号)。

    许多用例不需要不可重置的设备标识符。如果您的应用没有该权限,但您仍尝试查询标识符的相关信息。会返回空值或报错。

    设备唯一标识符需要特别注意,原来的READ_PHONE_STATE权限已经不能获得IMEI和序列号,如果想在Q设备上通过

    ((TelephonyManager) getActivity()      .getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId()

    获得设备ID,会返回空值(targetSDK<=P)或者报错(targetSDK==Q)。且官方所说的READ_PRIVILEGED_PHONE_STATE权限只提供给系统app,所以这个方法算是废了。

    谷歌官方给予了设备唯一ID最佳做法,但是此方法给出的ID可变,可以按照具体需求具体解决。

    本文给出一个不变和基本不重复的UUID方法。

    public static String getUUID() {
    
    String serial = null;
    
    String m_szDevIDShort = "35" +
            Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
    
            Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
    
            Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
    
            Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
    
            Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
    
            Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
    
            Build.USER.length() % 10; //13 位
    
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            serial = android.os.Build.getSerial();
        } else {
            serial = Build.SERIAL;
        }
        //API>=9 使用serial号
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    } catch (Exception exception) {
        //serial需要一个初始化
        serial = "serial"; // 随便一个初始化
    }
        //使用硬件信息拼凑出来的15位号码
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    }

    虽然由于唯一标识符权限的更改会导致android.os.Build.getSerial()返回unknown,但是由于m_szDevIDShort是由硬件信息拼出来的,所以仍然保证了UUID的唯一性和持久性。

    经测试上述方法完全相同的手机有可能重复,网上还有其他方案比如androidID,但是androidID可能由于机型原因返回null,所以个人任务两种方法半斤八两。设备ID的获取一个版本比一个版本艰难,如果有好的方法欢迎指出。

    minSDK警告

    在 Android Q 中,当用户首次运行以 Android 6.0(API 级别 23)以下的版本为目标平台的任何应用时,Android平台会向用户发出警告。

    如果此应用要求用户授予权限,则系统会先向用户提供调整应用权限的机会,然后才会允许此应用首次运行。

    谷歌要求运行在Q设备上的应用targetSDK>=23,不然会向用户发出警告。

    3.Q 行为变更:以 Android Q 为目标平台的应用

    非 SDK 接口限制

    非SDK接口限制在Android P中就已提出,但是在Q中,被限制的接口的分类有较大变化。

    非SDK接口介绍

    为了确保应用稳定性和兼容性,Android 平台开始限制您的应用可在 Android 9(API 级别 28)中使用哪些非 SDK 接口。Android Q 包含更新后的受限非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试)。

    非SDK接口限制就是某些SDK中的私用方法,如private方法,你通过Java反射等方法获取并调用了。那么这些调用将在target>=P或target>=Q的设备上被限制使用,当你使用了这些方法后,会报错:

    获取方法

    报错信息

     

     

    Dalvik instruction referencing a field

    NoSuchFieldError thrown

    Dalvik instruction referencing a method

    NoSuchMethodError thrown

    Reflection via Class.getDeclaredField() or Class.getField()

    NoSuchFieldException thrown

    Reflection via Class.getDeclaredMethod(), Class.getMethod()

    NoSuchMethodException thrown

    Reflection via Class.getDeclaredFields(), Class.getFields()

    Non-SDK members not in results

    Reflection via Class.getDeclaredMethods(), Class.getMethods()

    Non-SDK members not in results

    JNI via env->GetFieldID()

    NULL returned, NoSuchFieldError thrown

    JNI via env->GetMethodID()

    NULL returned, NoSuchMethodError thrown

    非SDK接口查找

    如果您不确定自己的应用是否使用了非 SDK 接口,则可以测试该应用进行确认。

    当你调用了非SDK接口时,会有类似Accessing hidden XXX的日志:

    Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)

    但是一个大项目到底哪里使用了这些方法,靠review代码和看日志肯定是不现实的,谷歌官方也提供了官方检查器veridex用来检测一个apk中哪里使用了非SDK接口。veridex下载。

    https://android.googlesource.com/platform/prebuilts/runtime/+/master/appcompat

    其中有windows,linux和mac版本,对应下载即可。

    下载解压后命令行cd到veridex目录下使用./appcompat.sh --dex-file=Q.apk即可自动扫描。Q.apk为包的绝对路径,如果包与veridex在相同目录下直接输入包文件名即可。

    扫描结果分为两部分,一部分为被调用的非SDK接口的位置,另一部分为非SDK接口数量统计,例如:

    1. greylist: 灰名单,即当前版本仍能使用的非SDK接口,但在下一版本中可能变成被限制的非SDK接口
    2. blacklist:黑名单,使用了就会报错。也是我们项目中必须解决的非SDK接口
    3. greylist-max-o:在targetSDK<=O中能使用,但是在targetSDK>=P中被限制的非SDK接口
    4. greylist-max-p:在targetSDK<=P中能使用,但是在targetSDK>=Q中被限制的非SDK接口

    所以从适配Q的角度出发,除了greylist我们可以暂时不解决以外,其余三种类型的非SDK接口需要我们进行适配。

    非SDK接口适配

    如果您的应用依赖于非 SDK 接口,则应该开始计划迁移到 SDK 替代方案。如果您无法为应用中的某项功能找到使用非 SDK 接口的替代方案,则应该请求新的公共 API。

    官方要求targetSDK>=P的应用不使用这些方法,并寻找其他的公共API去替代这些非SDK接口,如果找不到,则可以向谷歌申请,请求一个新的公共API https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces#feature-request (一般不需要)。

    就我个人扫描并定位的结果来看,项目中使用非SDK接口大概率有以下两种情况:

    1. 在自定义View的过程中为了方便,使用反射修改某个参数。
    2. 三方SDK中使用了非SDK接口(这种情况比较多)。

    第一种是好解决的,毕竟是我们自己写的代码。

    第二种就头疼了,只能更新到最新的三方SDK版本,或者提工单、换库(也是整个适配过程中工作量最庞大的部分)。

    4.项目升级遇到的问题

    模拟器X86,项目中SO库为v7

    • 找到so库源代码,编译成x86
    • 如果so库只是某个功能点使用,对APP整体没大影响,就可以屏蔽特定so库功能或略过测试
    • 如果so库是项目核心库必须加载,也可使用腾讯云测,上面有谷歌亲儿子Q版本。腾讯云测有adb远程连接调试功能(我没成功过)。adb连不上也没关系,直接安装就行,云测上也可以直接看日志。
    • 至于inter的houdini我尝试研究过,理论上能安装在x86模拟器上让它编译v7的so库,但是由于关于houdini的介绍比较少也比较旧,建议大家时间不充裕的话就别研究了。

    Requires development platform Q but this is a release platform.

    由于目前Q是preview版,所以targetSDK==Q 的应用只能在Q设备上跑。

    INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2

    这个错误是由于打包压缩so库时造成的,具体原因可见

    https://issuetracker.google.com/issues/37045367

    在AndroidManifest.xml的application节点下加入android:extractNativeLibs="true"

    可能有人加了上面代码还是不行,在app/build.gradle中的defaultConfig节点下加入

    packagingOptions{ 
          doNotStrip "/armeabi/.so" doNotStrip "/armeabi-v7a/.so" doNotStrip "/x86/.so" }

    Didn't find class “org.apache.http.client.methods.HttpPost"

    在AndroidManifest.xml的application节点下加入

    <uses-library android:name="org.apache.http.legacy" android:required="false"/>

    如果你的项目没有适配过android O或P,那么你需要注意:

    1. android O的读取已安装应用权限(对应用内自动更新有影响)
    2. android P的默认禁止访问http的API

    这两个版本的适配问题本文就不做详述,网上有很多详细的介绍。

    参考文献

    官方文档

    https://developer.android.com/preview

    出处:云栖社区 https://cloud.tencent.com/developer/article/1437956

    如果对您能有所帮助,请点个赞吧亲!!!

     

     

    展开全文
  • Android 10.0适配

    2020-04-27 09:37:12
    关于Android 10.0适配,看这篇就够了 Q行为变更:所有应用 用户隐私权限变更 AndroidQ引入了大量更改和限制以增强对用户隐私的保护。 官方文档将这一部分内容独立于Q 行为变更:所有应用来介绍,是因为这一部分...

    关于Android 10.0适配,看这篇就够了

    Q行为变更:所有应用
    用户隐私权限变更

    AndroidQ引入了大量更改和限制以增强对用户隐私的保护。

    官方文档将这一部分内容独立于Q 行为变更:所有应用来介绍,是因为这一部分内容庞大且重要,个人认为Q的最大更新就是用户隐私权限变更。具体变更的权限如下:

    权限;   受影响应用;   如何启用(影响范围)

     

    1、存储细化了

    (1)每个应用都有一个私有文件夹,访问私有文件夹无需权限

    (2)访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申请方法同原来的存储权限。

    3. 访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。

    4. 访问其他应用沙盒文件:如果你的应用需要使用其他应用在沙盒内创建的文件,请点击使用其他应用的文件,本文不做介绍。
     

    当满足以下每个条件时,将开启兼容模式,即不开启Q设备中的存储权限改动:

    应用targetSDK<=P。
    应用安装在从 Android P 升级到 Android Q 的设备上。
    但是当应用重新安装(更新)时,不会重新开启兼容模式,存储权限改动将生效。

    所以按官方文档所说,无论targetSDK是否为Q,必须对应用进行存储权限改动的适配。

     

    2、不能获得IMEI和序列号

    获得设备ID,会返回空值(targetSDK<=P)或者报错(targetSDK==Q)。且官方所说的READ_PRIVILEGED_PHONE_STATE权限只提供给系统app,所以这个方法算是废了。

    谷歌官方给予了设备唯一ID最佳做法,但是此方法给出的ID可变,可以按照具体需求具体解决。
     

    展开全文
  • 内容提供者 (获取媒体库图片显示出来)一级目录二级目录三级目录 Android 10.0 适配问题都解决了 一级目录 二级目录 三级目录

    内容提供者 (获取媒体库图片显示出来)


    Android 10.0 适配问题都解决了
    项目源码.

    呈现效果

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    设计步骤

    1. 第一步:先定义两个Activity,一个获取数据并用来展示,一个展示媒体库图片并提供选择
      MainActivity

       <Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:onClick="pickImages"
       android:text="获取图片"
       android:textSize="20dp" />
       <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/showPicM"
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
      

    PackageImageActivity

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/image3" />
    
        <TextView
            android:id="@+id/finished_pic"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="(0/9)完成"
            android:textColor="#000000"
            android:textSize="20dp"
            android:onClick="finishedPic"
            />
    
    </RelativeLayout>
    
    
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/showImages"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    
    1. 第二步: Android6.0以后的动态申请权限。

       private void checkReadPermission() {
               int readPermission = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
               int writePermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
               if (readPermission != PackageManager.PERMISSION_GRANTED && writePermission != PackageManager.PERMISSION_GRANTED) {
                   requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, READ_PERMISSION_CORD);
               }
           }
       	   @Override
       public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
           if (requestCode == READ_PERMISSION_CORD) {
               if (grantResults.length == 2 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                   Log.d(TAG, "拥有权限");
               }
           }
       }
      
    2. 第三步 从MainActivity 跳转到 PackageImageActivity

       	public void pickImages(View view) {
       	        Intent intent = new Intent(MainActivity.this, PackageImage.class);
       	        startActivity(intent);
       	    }
      

    4. 第四步 使用LoaderManager 异步获取数据

    	private void initLoaderManager() {
    	        imageItemList.clear();
    	        LoaderManager loaderManager = LoaderManager.getInstance(this);
    	        loaderManager.initLoader(LOADER_ID, null, new LoaderManager.LoaderCallbacks<Cursor>() {
    	            /**
    	             * 因为图库图片可能较多,不能在主线程进行耗时操作
    	             * 在子线程进行操作
    	             * @param id  
    	             * @param args
    	             * @return  一个CurSor 装载
    	             */
    	            @NonNull
    	            @Override
    	            public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
    
                if (id == LOADER_ID) {
                    return new CursorLoader(PackageImage.this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                            new String[]{MediaStore.Images.Media._ID}, null, null, MediaStore.Images.Media.DATE_ADDED+" DESC");
                }
                return null;
            }
    
            /**
             * 装载完成后调用
             * @param loader   一个对象
             * @param cursor   光标,包含所有图片信息。
             */
            @Override
            public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
                if (cursor != null) {
    
                    while (cursor.moveToNext()) {
    
                        String id = cursor.getString(0);
    	//                      **因为Android 10.0 以后进行分区存储。我们即使申请权限,也不能访问SD卡的内容。
    	//                      因此如果这里获取path 后面使用Glide 加载图片会报FileNoFOUND错误。所以我们通过ID获取Uri**
    	                        Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,id);
    	                        imageItemList.add(uri);
    	                        adapter.setData(imageItemList);
    	                    }
    	                }
    	            }
    
            /**
             * 重新装载是调用
             * @param loader 
             */
            @Override
            public void onLoaderReset(@NonNull Loader<Cursor> loader) {
    
            }
        });
        }
    
    1. 第五步 使用recyclerView 将获取的图片信息,展现出来

      @NonNull
          @Override
          public InnerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
              View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_image, parent, false);
              //使用该方法,获取屏幕长度,将每一个图片尺寸设置x/3
              Point point = SizeUtils.getScreenSize(view.getContext());
              RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(point.x / 3, point.y / 3);
              view.setLayoutParams(layoutParams);
              return new InnerHolder(view);
          }
      
    2. 第六步 在Adapter 里面暴露一个接口,用于将数据传出来

       public void setOnItemSelectedNum(onItemSelectedNum onItemSelectedNum){
               this.mOnItemSelectedNum = onItemSelectedNum;
           }
       
           public interface onItemSelectedNum{
               void onItemSelectedChange(List<Uri> list);
           }
      
    3. 第七步 设计事件,用于表示选中图片

       @Override
           public void onBindViewHolder(@NonNull InnerHolder holder, int position) {
               View view = holder.itemView;
               ImageView imageView = view.findViewById(R.id.iv);
               final View visibility = view.findViewById(R.id.visibility);
               final CheckBox select = view.findViewById(R.id.select);
               final Uri uri = mimageItems.get(position);
               Glide.with(imageView.getContext()).load(uri).into(imageView);
       //      先根据状态来渲染,防止紊乱
               if (mselect.contains(uri)) {
                   select.setButtonDrawable(R.drawable.image1);
                   visibility.setVisibility(View.VISIBLE);
               } else {
                   select.setButtonDrawable(R.drawable.image2);
                   visibility.setVisibility(View.INVISIBLE);
               }
       
               imageView.setOnClickListener(new View.OnClickListener() {
                   @Override
                   public void onClick(View v) {
       //                如果已经没被选择,添加
                       if (!mselect.contains(uri)) {
                           if(mselect.size()>=9){
                               Toast.makeText(select.getContext(),"最多选择"+MAX_SELECTED_PIC+"张图片",Toast.LENGTH_SHORT).show();
       
                           }else{
                               mselect.add(uri);
                               select.setButtonDrawable(R.drawable.image1);
                               visibility.setVisibility(View.VISIBLE);
                           }
       
                       } else {
       //                    如果已经被选择,删除,更改可见度
                           mselect.remove(uri);
                           select.setButtonDrawable(R.drawable.image2);
                           visibility.setVisibility(View.INVISIBLE);
                       }
                       mOnItemSelectedNum.onItemSelectedChange(mselect);
                   }
               });
           }
      
    4. 第八步 选择完成,将图片传回。需要使前后数据一致。我们使用一个PickerConfig(单例模式) 来实现。 在两个Activity都初始化它

       public class PickerConfig {
           //最大选择图片数量
           private int max_selected_pic = 1;
           private OnItemSelectedFinished monItemSelectedFinished = null;
       
           public OnItemSelectedFinished getMonItemSelectedFinished() {
               return monItemSelectedFinished;
           }
      
           private PickerConfig(){}
           private static PickerConfig pickerConfig;
       
           public static PickerConfig getInstance(){
               if(pickerConfig==null){
                   pickerConfig =new PickerConfig();
               }
               return pickerConfig;
           }
       
           public void setOnItemSelectedFinished(OnItemSelectedFinished onItemSelectedFinished){
               this.monItemSelectedFinished = onItemSelectedFinished;
           }
       
       
           public interface OnItemSelectedFinished{
               void onItemSelectedFinished(List<Uri> list);
           }
       
           public int getMax_selected_pic() {
               return max_selected_pic;
           }
       
           public void setMax_selected_pic(int max_selected_pic) {
               this.max_selected_pic = max_selected_pic;
           }
       }
      
    5. 第九步, 两个Activity初始化上面方法。 选择完成 ,数据返回

       public void finishedPic(View view){
       
               //需要获取数据
               List<Uri> list = adapter.getMselect();
               //把数据通知给其他地方
               PickerConfig.OnItemSelectedFinished monItemSelectedFinished = mpickerConfig.getMonItemSelectedFinished();
               if(monItemSelectedFinished!=null){
                   monItemSelectedFinished.onItemSelectedFinished(list);
               }
       
               //结束
               finish();
           } 
      
    6. 第十步,将获取的数据在MainActivity 使用recyclView实现。
      可以如果返回图片少于3个,就按几列展示。大于三列的三列显示

      private void initConfig() {
              PickerConfig pickerConfig = PickerConfig.getInstance();
              pickerConfig.setMax_selected_pic(MAX_SELECTED_PIC);
              pickerConfig.setOnItemSelectedFinished(new PickerConfig.OnItemSelectedFinished() {
                  @Override
                  public void onItemSelectedFinished(List<Uri> list) {
                      int horNum =1;
                      if(list.size()<3){
                          horNum =list.size();
                      }else{
                          horNum =3;
                      }
      
                      GridLayoutManager gridLayoutManager =new GridLayoutManager(MainActivity.this,horNum);
                      recyclerView.setLayoutManager(gridLayoutManager);
                      mainAdapter.setData(list,horNum);
                  }
              });
          }
      

    注意问题

    1. Android10.0 为了保存隐私,不允许访问SD卡的等内容。我们需要使用
    MediaStore.Images.Media 得到uri 不能使用path路径
    2. Glide4.x 以上版本需要
    implementation ‘com.github.bumptech.glide:glide:4.11.0’
    annotationProcessor ‘com.github.bumptech.glide:compiler:4.11.0’
    并且创建
    @GlideModule
    public class MyAppGlideModule extends AppGlideModule {
    }
    4. 如果没有报错,但图片没有加载出来。可能是因为适配器的设置长宽参数不合适

    展开全文
  • 记录Android10.0适配

    2020-10-31 20:46:55
    记录Android10.0适配 1. 文件读写 当编译SDK>=29时,由于Android10.0的新特性,即使用户授权了读写权限,也无法正常读写文件,所以就要重新适配适配方案如下: (1) 编译版本降到29以下,当然也不叫适配。...

    记录Android10.0的适配



    1. 文件读写

    当编译SDK>=29时,由于Android10.0的新特性,即使用户授权了读写权限,也无法正常读写文件,所以就要重新适配,适配方案如下:

    (1) 编译版本降到29以下,当然也不叫适配。

    (2) 10.0新特性,只允许操作系统给APP分配的文件夹,比如APP包名为【com.xxx.yyy】,那么,分配的文件夹就是【根目录】→【Android】→【data】→【com.xxx.yyy】适配代码如下:

    	/**
         * 获取SD卡根路径
         *
         * @return String 如果SD卡不存在则返回空字符串
         */
        public static String getSDCardPath(Context context) {
    
            String path = "";
    
            boolean isExist = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); // 判断sd卡是否存在
    
            if (isExist) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    context.getExternalFilesDir(null).getAbsolutePath();
                } else {
                    // 获取跟目录
                    path = Environment.getExternalStorageDirectory().getAbsolutePath();
                }
            }
    
            return path;
        }
    


    2. 调用系统相机

    (1) 在【res】下新建一个【xml】文件夹,然后新建一个【file_paths.xml】文件,内容如下(之前做个该适配的可以忽略本步骤):

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <!-- 这里设置根目录 -->
        <external-path
            name="file_root_path"
            path="." />
    </paths>
    

    (2) 在【AndroidManifest.xml】文件的【application】标签下,加入以下代码(之前做个该适配的可以忽略本步骤):

    		<provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="${applicationId}.hgFileProvider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
            </provider>
    

    【${applicationId}】替换为自己项目的【applicationId】,也可直接保留这样的写法,【hgFileProvider】可以替换为自己取的名字,下文中,均以【hgFileProvider】举例。

    (3)修改配置Uri的代码:

    唤起系统相机
    如图,把配置Uri的代码,封装成一个方法,方法如下:

    	/**
         * 获取图片文件的URI
         *
         * @param context Context
         * @param file    File
         * @return Uri
         */
        public Uri getImageUri(Context context, File file) {
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                return getOtherFileUri(context, file);
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                ContentValues contentValues = new ContentValues(1);
                contentValues.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
    
                return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
            }
    
            return Uri.fromFile(file);
        }
    
    	/**
         * 获取其他文件的URI
         *
         * @param context Context
         * @param file    File
         * @return Uri
         */
        public Uri getOtherFileUri(Context context, File file) {
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                return FileProvider.getUriForFile(context,
                        context.getApplicationContext().getPackageName() + ".hgFileProvider",
                        file
                );
            }
    
            return Uri.fromFile(file);
        }
    


    3. 自定义选图片或文件模块失效

    和问题【1】类似,由于10.0的新特性导致只能读写系统分配的文件夹。此时只需要在【AndroidManifest.xml】的【application】的标签中,加入一个配置即可:

    	<application
            ···
            android:requestLegacyExternalStorage="true">
    			···
    	</application>
    

    网上其他资料说问题【1】的第3种解决方案,就是此问题的解决方法,我没有尝试,因为本博客的书写顺序就是我发现问题的顺序,所以也不高兴回过头去尝试。



    其他

    因为本人手头没有10.0的手机,问题都是其他朋友或客户发现反馈的,所以适配进度会比较慢,往后再发现新问题会继续更新博客。

    展开全文
  • 关于Android 10.0适配

    2020-07-22 13:53:08
    本文将从三个角度介绍Android Q的部分适配问题,也是大家开发适配过程中大概率会遇到的问题: Q 行为变更:所有应用 (不管targetSdk是多少,对所有跑在Q设备上的应用均有影响) Q 行为变更:以 Android Q 为目标...
  • 原始的需求是这样的,客户会在系统中预装多个应用,但某些应用是没有经过适配的,客户要求的像素密度是160,但某些应用在该像素密度下显示会显得很小。客户不想改应用,要求在该160的像素密度下,也要能够正常显示...
  • 本文将从三个角度介绍Android Q的部分适配问题,也是大家开发适配过程中大概率会遇到的问题: Q 行为变更:所有应用 (不管targetSdk是多少,对所有跑在Q设备上的应用均有影响) Q 行为变更:以 Android Q 为目标...
  • 写和读的权限配置好后 也动态获取了权限 但使用BitmapFactory的时候还有提示权限拒绝 在anroid9.0及以下的都可以 但android10.0出现了问题这个时候我们在 清单文件里面加入这个配置就可以了。 1、 清单文件...
  • 华为EMUI 10.0功能及适配机型 华为安卓系统和鸿蒙OS区别根据最新消息显示,华为终端官方再次给出消息称,在8月9日华为开发者大会首天,他们将发布新一代基于Android Q(也叫做Android10.0)的手机系统EMUI 10.0。...
  • exagear模拟器安卓10.0

    2021-06-07 16:46:18
    软件简介exagear模拟器安卓10.0游戏是一款强大的游戏模拟器,这款模拟器可以轻松的模拟更多有趣的PC游戏,可以让玩家体验到热门火爆的游戏内容,在手机端轻松的安装下载,时刻享受端游带来的魅力,感慨下载体验吧。...
  • 本文将重点介绍该版本特性中部分需要开发适配的部分。汇总自己踩过的坑以及百度所得,本文会持续更新。 背景:谷歌在GoogleI / O 2019发布了Android Q Beta X。对当前应用影响重大,急需适配。 API = 29;主要影响...
  • 我用的是 PictureProgressBar 4、 FileProvider的使用 5、android 10.0 的保存适配 代码实现: onCreate方法 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);...
  • Android Q(10.0)版本新特性和兼容性适配

    万次阅读 热门讨论 2019-04-18 12:02:29
    北京时间2019年3月14日Google正式对外发布Android Q Beta 1及预览版SDK,这意味着安卓开发者们又即将迎来一年一度的新版本适配工作了。Android Q 为开发者们带来了许多新功能,如折叠屏增强项、新网络连接 API、全新...
  • &lt;uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /&gt; 添加这个权限。即可
  • 该楼层疑似违规已被系统折叠隐藏此楼查看此楼附一下保存图片的函数:public void saveimg(){ if (bitmap2==null){ Toast toast=Toast.makeText(MainActivity.this,"图像尚未处理,无法保存",Toast.LENGTH_SHORT);...
  • 分享给微信好友或朋友圈摘要:Andorid10支持手机型号目前正在逐渐公布,许多小伙伴们不知道自己想要入手的手机支不支持,不用担心,我们将为您带来安卓10适配机型介绍。Andorid10支持手机型号目前正在逐渐公布,许多...
  • android 10.0 降级适配 非正常适配

    千次阅读 2020-01-16 11:04:04
    bug 1 Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PRO 首先 targetVersion 降级到28 其次 ... android:enabled="true" tools:targe...
  • 安卓手机适配

    2015-08-26 17:28:15
    支持安卓多屏幕分辨率适配,完美适配机型,针对适配难题不是问题,大家不用担心
  • if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { mChannel = new NotificationChannel("service_01", getString(R.string.app_name),NotificationManager.IMPORTANCE_LOW);...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,867
精华内容 746
关键字:

安卓10.0适配