精华内容
下载资源
问答
  • Android Q 适配

    千次阅读 2019-09-16 20:10:51
    因为项目在华为部分手机有预装,应华为要求,适配 Android QAndroid 10) 版本,因为华为那边要求,新版本系统出来不久就会适配,项目是一步步适配上来的,Android M、Android N、Android O、Android P ,所以本次...

    因为项目在华为部分手机有预装,应华为要求,适配 Android Q(Android 10) 版本,因为华为那边要求,新版本系统出来不久就会适配,项目是一步步适配上来的,Android M、Android N、Android O、Android P ,所以本次适配是从 Android P (9.0) 升到 Android Q,所以适配难度不是很大。建议新版本出来稳定后还是及时适配,否则一下跳跃升级适配上会比较麻烦。下面是我们项目适配遇到的问题,后面遇到问题再继续补充:

    主要升级targetSdkVersion到29就可以了,我将编辑版本升到29了,support库用的是28.0.0,怕第三方库不支持没升androidx。#### Android Q (10.0)(API 29) 适配
    因为项目在华为部分手机有预装,应华为要求,适配 Android Q(Android 10) 版本,因为华为那边要求,新版本系统出来不久就会适配,项目是一步步适配上来的,Android M、Android N、Android O、Android P ,所以本次适配是从 Android P (9.0) 升到 Android Q,所以适配难度不是很大。建议新版本出来稳定后还是及时适配,否则一下跳跃升级适配上会比较麻烦。下面是我们项目适配遇到的问题,后面遇到问题再继续补充:

    主要升级targetSdkVersion到29就可以了,我将编辑版本升到29了,support库用的是28.0.0,怕第三方库不支持没升androidx。

    targetSdkVersion : 29,
    compileSdkVersion: 29,
    buildToolsVersion: "29.0.2",
    

    应用读取 Device ID

    Android Q 之前有如下代码,获取设备Id,IMEI等

    TelephonyManager telManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    telManager.getDeviceId();
    telManager.getImei();
    

    添加下面权限,并且需要动态申请权限

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    

    在 Android Q 上调用上面方法会报错

    java.lang.SecurityException: getDeviceId: The user 10143 does not meet the requirements to access device identifiers.
    

    在 Android Q 上上面方法已经不能使用了,如果获取设备唯一Id,需要使用其他方式了,谷歌提供的获取唯一标识符做法见 文档,也可以用Android_ID,上面这些也不是绝对能得到一个永远不变的Id,可能需要多种方案获取其他Id,比如有谷歌商店的手机可以使用谷歌提供的广告Id,其他厂商现在也会提供手机的一个唯一Id,主流的华为、小米、oppo、vivo都有,我们项目现在获取唯一Id的方式大致如下:

            public String getUniqueId(Context context) {
            if (!TextUtils.isEmpty(uniqueId)) {
                return uniqueId;
            }
    
            // 获取本地文件保存的数据
            uniqueId = getIdFromLocal(context);
    
            if (TextUtils.isEmpty(uniqueId)) {
    
                uniqueId = getAndroidId(context);
    
                if (TextUtils.isEmpty(uniqueId)) {
                    uniqueId = UUID.randomUUID().toString();
                }
                saveDeviceId(uniqueId);
            }
    
            return uniqueId;
        }
    
        private String getAndroidId(Context context) {
            String generateId = null;
    
            try {
                final String androidId = Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
    
                if (TextUtils.isEmpty(androidId) || "9774d56d682e549c".equals(androidId)) {
                    generateId = Installation.id(context);
                } else {
                    UUID uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
                    generateId = uuid.toString();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return generateId;
        }
        
    

    主要就是获取AndroidId作为唯一Id,然后会存在 SharedPreferences 和 本地文件中各一份,后面就会从本地中读取,主要不要存放在App专属目录,否则跟随App卸载就没了,下面代码是Android Q 中在公共目录存取id的方法。

        private static final String DEVICE_UUID_FILE_NAME_IN_PUBLIC = "android_dev.d";
        
        private void saveIdByPublic(Context context, String id) {
            if (TextUtils.isEmpty(id))
                return;
    
            new Thread(() -> {
                ParcelFileDescriptor parcelFileDescriptor = null;
                FileOutputStream fileOutputStream = null;
                try {
                    ContentValues values = new ContentValues();
                    values.put(MediaStore.Downloads.DISPLAY_NAME, DEVICE_UUID_FILE_NAME_IN_PUBLIC);
                    values.put(MediaStore.Downloads.TITLE, "device_id");
                    values.put(MediaStore.Downloads.MIME_TYPE, "file/plain");
    //                    values.put(MediaStore.Downloads.IS_PENDING, 1);
                    ContentResolver cr = context.getContentResolver();
                    Uri uri = cr.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
                    parcelFileDescriptor = cr.openFileDescriptor(uri, "w");
                    fileOutputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
                    fileOutputStream.write(id.getBytes());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    IOCloseHelper.closeQuietly(parcelFileDescriptor);
                    IOCloseHelper.closeQuietly(fileOutputStream);
                }
            }).start();
        }
    
        private String getIdByPublic(Context context) {
            ParcelFileDescriptor pfd = null;
            FileInputStream inputStream = null;
            try {
                Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
                ContentResolver resolver = context.getContentResolver();
    
                String selection = MediaStore.Downloads.DISPLAY_NAME + "=?"; // 查询条件
                String[] args = new String[]{DEVICE_UUID_FILE_NAME_IN_PUBLIC}; // 上面?的值
                String[] projection = new String[]{MediaStore.Downloads._ID}; // 查询的内容
                Cursor cursor = resolver.query(external, projection, selection, args, null);
                Uri uri = null;
    
                if (cursor != null && cursor.moveToFirst()) {
                    uri = ContentUris.withAppendedId(external, cursor.getLong(0));
                    cursor.close();
                }
    
                pfd = context.getContentResolver().openFileDescriptor(uri, "r");
                inputStream = new FileInputStream(pfd.getFileDescriptor());
                StringBuilder sb = new StringBuilder();
                byte[] buffer = new byte[100];
                int readCount;
                while ((readCount = inputStream.read(buffer)) != -1) {
                    sb.append(new String(buffer, 0, readCount));
                }
    
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                IOCloseHelper.closeQuietly(pfd);
                IOCloseHelper.closeQuietly(inputStream);
            }
    
            return null;
        }
    

    文件存储

    在早期的测试版本新增了READ_MEDIA_IMAGESREAD_MEDIA_AUDIOREAD_MEDIA_VIDEO三个权限,正式版已经移除,还是使用之前的两个读写权限

        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    

    在 Android Q 之前可以访问SD卡任意目录,使用如下:

    File file = Environment.getExternalStorageDirectory();
    

    上面得到的是SD卡根目录,打印出路径为:/storage/emulated/0。在 Android Q 上已经不能访问这个目录了,Android Q 下文件存储看下面方法。

    App 专属目录

    在 App专属目录下本App可以随意操作,无需申请权限,不过 App专属目录会在App卸载时跟随删除。看下面几个目录(通过Application的context就可以访问)。

    • getFilesDir() :/data/user/0/本应用包名/files

    • getCacheDir():/data/user/0/本应用包名/cache

    • getExternalFilesDir(null):/storage/emulated/0/Android/data/本应用包名/files

    • getExternalCacheDir():/storage/emulated/0/Android/data/本应用包名/cache

    getFilesDir和getCacheDir是在手机自带的一块存储区域(internal storage),通常比较小,SD卡取出也不会影响到,App的sqlite数据库和SharedPreferences都存储在这里。所以这里应该存放特别私密重要的东西。

    getExternalFilesDir和getExternalCacheDir是在SD卡下(external storage),在sdcard/Android/data/包名/files和sdcard/Android/data/包名/cache下,会跟随App卸载被删除。

    files和cache下的区别是,在手机设置-找到本应用-在存储中,点击清除缓存,cache下的文件会被删除,files下的文件不会。

    谷歌推荐使用getExternalFilesDir。我们项目的下载是个本地功能,下载完成后是存本地数据库的,不是放网络上的,所以下载的音视频都放到了这下面,项目卸载时跟随App都删除了。getExternalFilesDir方法需要传入一个参数,传入null时得到就是sdcard/Android/data/包名/files,传入其他字符串比如"Picture"得到sdcard/Android/data/包名/files/Picture。

    使用MediaStore访问公共目录

    通过上面App专属目录只能操作本App专属目录,并且保存的文件会随着App卸载删除。通过MediaStore,App可以访问公共目录下的媒体文件,通过MediaStore操作Uri读写文件。

    保存图片直接用 insertImage 方法就可以,可以传入Bitmap或图片在本地的路径,注意本地路径要是本App可以访问到的路径,否则没权读取

    public void saveImage(String imagePath, String title, String desc) {
    	MediaStore.Images.Media.insertImage(context.getContentResolver(), imagePath, title, desc);
    }
    或
    public void saveImage(Bitmap bitmap, String title, String desc) {
    	MediaStore.Images.Media.insertImage(context.getContentResolver(), bitmap, title, desc);
    }
    

    其他类型的文件保存就没有直接的方法了,大致可以用下面这样:

        public void saveFile(final Uri extUri, final String mimeType, final String saveName, final String desc,
                              final String netUrl) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        ContentValues values = new ContentValues();
                        values.put(MediaStore.Images.Media.DISPLAY_NAME, saveName);
                        values.put(MediaStore.Images.Media.TITLE, saveName);
                        values.put(MediaStore.Images.Media.DESCRIPTION, desc);
                        values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
                        ContentResolver cr = context.getContentResolver();
                        Uri uri = cr.insert(extUri, values);
    
                        byte[] buffer = new byte[1024];
                        ParcelFileDescriptor parcelFileDescriptor = cr.openFileDescriptor(uri, "w");
                        FileOutputStream fileOutputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
                        URL url = new URL(netUrl);
                        InputStream inputStream = url.openStream();
                        while (true) {
                            int numRead = inputStream.read(buffer);
                            if (numRead == -1) {
                                break;
                            }
                            fileOutputStream.write(buffer, 0, numRead);
                        }
                        fileOutputStream.close();
                        parcelFileDescriptor.close();
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        // TODO close io
                    }
                }
            }).start();
        }
    

    看上面代码,前面得到uri,然后变为fileOutputStream,后面就是文件的读写了,inputStream也可以同过其他方式得到(比如本地文件等),有输入流就可以写到uri中了。

    使用如下:

    // 保存图片
    saveFile(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/png", 
    "myImage", "", "http://www.xxx.png");
    
    // 保存视频
    saveFile(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", 
    "myVideo", "", "http://www.xxx.mp4");
    
    // 保存音频
    saveFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, "audio/mpeg", 
    "myAudio", "", "http://www.xxx.mp3");
    
    // Android Q 新增的下载目录
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
    	saveFile(MediaStore.Downloads.EXTERNAL_CONTENT_URI, "text/plain", 
    	"myText", "", "http://www.xxx.txt");
    }
    

    文件读取,以读取图片为例,其他的也一样

    获取全部图片:

        public static List<Uri> loadPhotoFiles(Context context) {
            List<Uri> photoUris = new ArrayList<Uri>();
            Cursor cursor = context.getContentResolver().query(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.Media._ID}, null, null, null);
            while (cursor.moveToNext()) {
                int id = cursor.getInt(cursor
                        .getColumnIndex(MediaStore.Images.Media._ID));
                Uri photoUri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + File.separator + id);
                photoUris.add(photoUri);
            }
            return photoUris;
        }
        
         // uri 转 bitmap
         public static Bitmap getBitmapFromUri(Uri uri) throws IOException {
            ParcelFileDescriptor parcelFileDescriptor =
                    context.getContentResolver().openFileDescriptor(uri, "r");
            FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
            Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
            parcelFileDescriptor.close();
            return image;
        }
    

    根据title获取图片:

        private Bitmap getImage(String title) {
            Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            ContentResolver resolver = context.getContentResolver();
    
            String selection = MediaStore.Images.Media.TITLE + "=?"; // 查询条件
            String[] args = new String[]{title}; // 上面?的值
            String[] projection = new String[]{MediaStore.Images.Media._ID}; // 查询的内容
            Cursor cursor = resolver.query(external, projection, selection, args, null);
            Uri imageUri = null;
    
            if (cursor != null && cursor.moveToFirst()) {
                imageUri = ContentUris.withAppendedId(external, cursor.getLong(0));
                cursor.close();
            }
    
            if (imageUri == null) {
                return null;
            }
    
            ParcelFileDescriptor pfd = null;
            try {
                pfd = getContentResolver().openFileDescriptor(imageUri, "r");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            if (pfd != null) {
                Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
                pfd.close();
                return bitmap;
            }
            
            return null;
        }
    

    注意:MediaStore的DATA 在Android Q 之前表示文件的真实路径,在Android Q 被废弃,可以通过 _ID 获取Uri,通过 ContentUris.withAppendedId(external, cursor.getLong(0)); 获取。

    删除文件,需要先查询出uri

    context.getContentResolver().delete(imageUri, null, null);
    

    修改文件,用的比较少

      // 修改的内容以键值对放到ContentValues中
      ContentValues values = new ContentValues();
      values.put("title", "new title");
      getContentResolver().update(imageUri, values, null, null);
    
    使用SAF访问指定目录

    存储访问框架(Storage Access Framework),这种方式操作文件时会拉起系统页面,通过用户授权操作来完成文件读取,用户可以选择任何目录,用户选完后App就有了这个目录的读写权限。官方文档

    保存一个文件时,用下面方法

        private void createFile(String fileName, String mimeType) {
            Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            if (!TextUtils.isEmpty(mimeType)) {
                intent.setType(mimeType);
            }
            intent.putExtra(Intent.EXTRA_TITLE, fileName);
            startActivityForResult(intent, REQUEST_CODE);
        }
        
            @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
                Uri uri = null;
                if (data != null) {
                    uri = data.getData();
                    final int takeFlags = getIntent().getFlags()
                            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    // Check for the freshest data.
                    getContentResolver().takePersistableUriPermission(uri, takeFlags);
                    writeFile(uri, netUrl);
                }
            }
        }
        
        private void writeFile(Uri uri, String netUrl) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "w");
                        FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
    
                        byte[] buffer = new byte[1024];
                        URL url = new URL(netUrl);
                        InputStream inputStream = url.openStream();
                        while (true) {
                            int numRead = inputStream.read(buffer);
                            if (numRead == -1) {
                                break;
                            }
                            fileOutputStream.write(buffer, 0, numRead);
                        }
    
                        fileOutputStream.close();
                        pfd.close();
                        inputStream.close();
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    

    调用createFile会拉起下面界面,用户保存后会回调到onActivityResult中,并将uri传来,得到uri后就可以写入了。
    如下图就是通过SAF保存视频会拉起系统界面,让用户选择授权,读取删除等都差不多是这样一个界面,下面就不截图了。
    在这里插入图片描述

    读取一个文件,以读取图片为例:

        public void openImage() {
            Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType("image/*");
            startActivityForResult(intent, READ_REQUEST_CODE);
        }
        
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
                Uri uri = null;
                if (data != null) {
                    uri = data.getData();
    
                    showImage(uri);
                }
            }
        }
    
        private void showImage(Uri uri) {
            if (uri == null)
                return;
            ParcelFileDescriptor pfd = null;
            try {
                pfd = getContentResolver().openFileDescriptor(uri, "r");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            if (pfd != null) {
                Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
                pdf.close();
                ImageView imageView = findViewById(R.id.imageView);
                imageView.setImageBitmap(bitmap);
            }
        }
    

    删除文件,跟上面读取一样,在onActivityResult中调用deleteImage,代码如下:

        private void deleteImage(Uri uri) {
            final int takeFlags = getIntent().getFlags()
                    & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            // Check for the freshest data.
            getContentResolver().takePersistableUriPermission(uri, takeFlags);
            try {
                DocumentsContract.deleteDocument(getContentResolver(), uri);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    

    修改文件,应该也是通过Intent.ACTION_OPEN_DOCUMENT打开选择一个文件,最后在onActivityResult中得到选择文件的uri,再修改,没有具体使用过。

    下面是我们项目在文件存储中遇到的问题

    1、更新图片到图库

    保存图片后需要更新到相册,之前下载图片到App 专属目录,然后通过方法1同步到相册,让用户在相册能看到下载的图片。在 Android Q 上面使用方法1不生效,应该是相册访问不到App专属目录,现在做法是通过方法2将图片存到公共目录。

    // 方法1
    Uri uri = Uri.fromFile(file);
    sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
    
    // 方法2
    // 可以看上面文件存储,也可以传入Bitmap
    MediaStore.Images.Media.insertImage(getContentResolver(), file.getAbsolutePath(), name, "");
    
    2、文件上传下载

    上传:如果是App内部使用,则可以选择上传 App 专属目录下的文件,使用不需要修改;如果是想选择任意目录的文件,可以使用 SAF 的方式;如果选择系统公共目录下的文件可以使用 MediaStore 方式。在Android Q上上传非App 专属目录下的文件,上传时通过File的方式上传是不可以的,我们项目使用的是 MediaStore 方式,通过 MediaStore 得一个 Uri,然后转为 InputStream 上传,方法见下面getInputStream,大部分上传文件库应该都支持传入一个 InputStream,因为最终上传也是要获取到一个 InputStream。如果非要通过File上传文件或者需要对File做一些特殊的操作的话,最简单的方案可以将公共目录下的文件拷贝到 App 专属目录下就可以随意操作了,方法见下面 getFile。

    下载:如果是App内部使用,则可以选择下载到 App 专属目录下,使用不需要修改;如果是想下载到任意目录,可以使用 SAF 的方式;如果App卸载后文件不跟随删除可以使用 MediaStore 方式。我们项目大部分都是下载到App 专属目录下了,一下卸载App后保留的文件,是通过MediaStore下载到公共目录了。

        public static InputStream getInputStream(android.net.Uri uri) {
            InputStream inputStream = null;
            try {
                ContentResolver cr = context.getContentResolver();
                inputStream = cr.openInputStream(uri);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return inputStream;
        }
    
            /**
         * 拷贝文件,将uri拷贝到 App专属目录下
         * @param uri 要拷贝文件的Uri
         * @param saveName 保存到专属目录下的文件名
         * @return 拷贝后新的文件
         */
        public static File getFile(Uri uri, String saveName) {
            File rootFile = context.getExternalFilesDir(null);
            File file = new File(rootFile, saveName);
    
            try {
                byte[] buffer = new byte[1024];
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                InputStream inputStream = context.getContentResolver().openInputStream(uri);
                while (true) {
                    int numRead = inputStream.read(buffer);
                    if (numRead == -1) {
                        break;
                    }
                    fileOutputStream.write(buffer, 0, numRead);
                }
                fileOutputStream.close();
                inputStream.close();
            } catch (IOException e) {
                file = null;
                e.printStackTrace();
            }
    
            return file;
        }
    

    参考链接:Android Q 要来了,给你一份很"全面"的适配指南!

    展开全文
  • AndroidQ(十)Android Q功能和API

    千次阅读 2019-04-28 17:14:37
    Android Q 功能和 API Android Q 为用户和开发者引入了强大的新功能。本文重点介绍面向开发者的新功能。 要了解新版 API,请阅读API 差异报告或访问Android API 参考。为醒目起见,将突出显示新版 API。此外,请...

    Android Q 功能和 API

    Android Q 为用户和开发者引入了强大的新功能。本文重点介绍面向开发者的新功能。

    要了解新版 API,请阅读 API 差异报告或访问 Android API 参考。为醒目起见,将突出显示新版 API。此外,请务必查阅 Android Q 行为变更(针对以 Android Q 为目标平台的应用所有应用)以及隐私权变更,以了解平台变更可能给您的应用带来哪些方面的影响。

    安全增强功能

    Android Q 引入了若干安全功能,详见以下各节摘要说明:

    改进了生物识别身份验证对话框

    Android Q 对 Android 9 中增加的统一生物识别身份验证对话框进行了以下改进:

    指定用户确认要求

    您现在可以提供一个提示,以告知系统在用户使用隐式生物识别模式完成身份验证后无需要求用户进行确认。例如,您可以告知系统,在用户使用面孔身份验证完成身份验证后无需进行进一步确认。

    默认情况下,系统会要求用户进行确认。通常,用户希望确认敏感或高风险的操作(例如,购买商品)。但是,如果您的应用存在某些低风险操作,那么您就可以将 false 传递至 setRequireConfirmation() 方法,提供不要求用户确认的提示。由于此标记作为提示传递到系统,因此如果用户更改了针对生物识别身份验证的系统设置,则系统可能会忽略相应的值。

    改进了对设备凭据的回退支持

    您现在可以告知系统,如果用户因某种原因而无法使用其生物识别输入设备,则可以使用设备 PIN 码、图案或密码来进行身份验证。要启用此回退支持,请使用 setAllowDeviceCredential() 方法。

    如果您的应用目前使用 createConfirmDeviceCredentialIntent() 来回退到设备凭据,请改为使用新方法。

    检查设备的生物识别功能

    现在,您可以在调用 BiometricPrompt 之前,先使用 BiometricManager 类中的 canAuthenticate() 方法检查设备是否支持生物识别身份验证。

    直接从 APK 运行嵌入式 DEX 代码

    您现在可以告知平台直接从应用的 APK 文件中运行嵌入式 DEX 代码。如果攻击者曾设法篡改了设备上本地编译的代码,则此选项有助于防止相应攻击。

    注意:启用此功能可能会影响应用的性能,因为在应用启动时 ART 必须使用 JIT 编译器(而不是读取提前编译的原生代码)。我们建议您先测试应用性能,然后再决定是否在已发布的应用中启用此功能。

    要启用此功能,请在应用清单文件的 <application> 元素中将 android:useEmbeddedDex 属性的值设为 true。您还必须编译一个 APK,其中要包含 ART 可以直接访问的未压缩 DEX 代码。将以下选项添加到 Gradle 或 Bazel 配置文件,以编译包含未压缩 DEX 代码的 APK:

    Gradle

    aaptOptions {

           noCompress 'dex'

        }

       

     

    Bazel

        android_binary(

           ...,

           nocompress_extensions = [“.dex”],

        )

        

     

     

    TLS 1.3 支持

    现在,平台的 TLS 实现支持 TLS 1.3。TLS 1.3 是 TLS 标准的主要修订版本,它提升了性能和安全性。我们的基准测试数据表明,与 TLS 1.2 相比,使用 TLS 1.3 可以将建立安全连接的速度提高 40%。

    默认情况下,系统会为所有 TLS 连接启用 TLS 1.3。您可以通过调用 SSLContext.getInstance("TLSv1.2") 来获取停用 TLS 1.3 的 SSLContext。您还可以对相关对象调用 setEnabledProtocols() 来为每个连接来启用或停用协议版本。

    以下是有关 TLS 1.3 实现的一些重要的详细信息:

    • TLS 1.3 加密套件不可自定义。在启用 TLS 1.3 后,受支持的 TLS 1.3 加密套件会始终保持启用状态,并且系统会忽略所有试图通过调用 setEnabledCipherSuites() 将其停用的行为。
    • 在协商 TLS 1.3 时,系统会在将会话添加到会话缓存之前调用 HandshakeCompletedListeners(这与 TLS 1.2 和之前的其他版本不同)。
    • 在之前本应抛出 SSLHandshakeException 的某些情况下,SSLEngine 实例会抛出 SSLProtocolException
    • 不支持 0-RTT 模式。

    公共 Conscrypt API

    现在,Conscrypt 安全提供程序包含适用于 TLS 功能的公共 API。过去,用户可以通过反射来访问此功能。但是,由于在 Android P 中增加了关于调用非公共 API 的限制,因此这已在 Android Q 中加入了灰名单,并将在未来版本中进一步受限。

    此更新在 android.net.ssl 下增加了一组类,这些类包含用于访问通用 javax.net.ssl API 不提供的功能的静态方法。这些类的名称为相关 javax.net.ssl 类的复数,用户可以由此推断是否为这些类。例如,在 javax.net.ssl.SSLSocket 实例中运行的代码可以使用新的 android.net.ssl.SSLSockets 类中的方法。

    ELF TLS

    使用 API 级别 29 及更高版本的 NDK 编译的应用无需再使用 emutls,但可以改为使用 ELF TLS。我们增加了对动态和静态链接器的支持,以支持处理线程局部变量的新方法。

    对于针对 API 级别 28 及更低版本编译的应用,我们实现了针对 libgcc/compiler-rt 的改进,以便解决一些 emutls 问题。

    有关详情,请参阅面向 NDK 开发者的 Android 变更

    连接功能

    Android Q 包含一些与网络和连接相关的改进。

    WLAN 网络连接 API

    Android Q 增加了对点对连接的支持。借助此功能,应用可以提示用户更改设备连接到的接入点。点对点连接用于“非网络提供”目的,例如 Chromecast 和 Google Home 硬件等辅助设备的引导配置。

    点对点连接不需要位置权限。如果您发起连接到对等设备的请求,便会在对方设备上启动一个对话框,让相应设备的用户可以通过此对话框来接受连接请求。

    WLAN 网络建议 API

    Android Q 扩大了支持范围,现在允许应用提示用户连接到 WLAN 接入点。您可以提供关于要连接到哪个网络的建议。平台最终会根据来自您的应用和其他应用的输入来选择要接受的接入点。

    来自应用的建议必须先得到用户批准,然后平台才会发起到建议的网络的连接。当平台第一次在扫描结果中找到与应用提供的其中一个建议相匹配的网络时,将由用户响应通知进行批准。当平台连接到建议的其中某个网络时,设置会显示相关文本以将网络连接归因于提出建议的相应应用。

    改进了 WLAN 高性能和低延迟模式

    借助 Android Q,您可以为底层调制解调器提供提示,以最大限度地缩短延迟。

    Android Q 扩展了 WLAN Lock API,以有效地支持高性能和低延迟模式。系统会针对高性能和低延迟模式停用 WLAN 节能模式,并且您可以在低延迟模式下启用进一步的延迟优化(具体取决于调制解调器支持)。

    仅当获取锁的应用在前台运行且屏幕处于开启状态时才能启用低延迟模式。低延迟模式对实时移动游戏应用尤其有用。

    DNS 解析器中的专用查找

    先前版本的 Android 信息和运营商服务使用 org.xbill.DNS2 软件包来执行 NAPTR 和 SRV 查询。Android Q 增加了对“通过传输层安全协议 (TLS) 执行 DNS”的原生支持,以进行专用 DNS 查找。这项新增功能可以为开发者提供标准明文查找和“通过传输层安全协议 (TLS) 执行 DNS”模式。

    WLAN Easy Connect

    借助 Android Q,您可以利用 Easy Connect 为使用 URI 的对等设备配置 WLAN 凭据。您可以通过各种方法来检索此 URI,包括从 QR 码或者通过蓝牙 LE 或 NFC。

    当 URI 可用时,您可以立即使用 ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE Intent 来配置对等设备的 WLAN 凭据。这样一来,用户就可以选择 WLAN 网络以共享并安全地传输相关凭据。

    注意:在使用此 Intent 之前,应用必须先通过调用 WifiManager.isEasyConnectSupported() 来验证设备是否支持 Easy Connect。

    WLAN 直连连接 API

    WifiP2pConfig 和 WifiP2pManager API 类在 Android Q 中有更新,以支持利用预先确定的信息快速与 WLAN 直连建立连接的功能。此信息通过边信道进行共享,例如蓝牙或 NFC。

    以下代码示例显示了如何使用预先确定的信息来创建群组:

        val manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager

        val channel = manager.initialize(this, mainLooper, null)

     

        // prefer 5G band for this group

        val config = WifiP2pConfig.Builder()

            .setNetworkName("networkName")

            .setPassphrase("passphrase")

            .enablePersistentMode(false)

            .setGroupOperatingBand(WifiP2pConfig.GROUP_OWNER_BAND_5GHZ)

            .build()

     

        // create a non-persistent group on 5GHz

        manager.createGroup(channel, config, null)

       

     

    要使用凭据加入群组,请将 manager.createGroup() 替换为以下内容:

        manager.connect(channel, config, null)

       

     

    蓝牙 LE 连接导向型频道 (CoC)

    借助 Android Q,您的应用可以使用 BLE CoC 连接在两个 BLE 设备之间传输较大的数据流。此接口抽象化处理了蓝牙和连接机制,以简化实现。

    电话功能

    Android Q 包含一些与电话相关的改进。

    通话质量方面的改进

    Android Q 增加了在支持相关功能的设备上收集进行中的 IP 多媒体子系统 (IMS) 通话质量相关信息的功能,包括通过网络接听和拨打电话的质量。

    选接电话和来电显示

    Android Q 让您的应用可以将用户通讯录中不存在号码的来电标识为潜在骚扰电话,以及代表用户拒接骚扰电话而不响铃。系统会在通话记录中将这些已屏蔽的来电的相关信息记录为已屏蔽的来电,以便用户更清楚地了解何时有过未接来电。使用此新版 API,不需要从用户获取 READ_CALL_LOG 权限就可以提供选接电话和来电显示功能。

    您还可以配置应用,让其提供来电者的相关信息(例如公司名称、Facebook 联系信息等),这些信息会显示在平台的拨号器应用中。

    Call Redirection Service API

    Android Q 更改了来电 Intent 的处理方式。我们弃用了 NEW_OUTGOING_CALL 广播,并将其替换为 CallRedirectionService API。CallRedirectionService 提供了相关接口,以供您修改 Android 平台拨打的去电。例如,第三方应用可能会取消通话并通过 VoIP 对其进行重新路由。

    媒体和图形

    Android Q 引入了以下媒体和图形方面的新功能和 API:

    Native MIDI API

    借助 Android Native MIDI API (AMidi),应用开发者可以使用 C/C++ 代码发送和接收 MIDI 数据、与 C/C++ 音频/控制逻辑进行更紧密的集成以及最大限度地减少对 JNI 的需求。

    有关详情,请参阅 Android Native MIDI API

    MediaCodecInfo 方面的改进

    MediaCodecInfo 中有一些新方法可以显示有关编解码器的更多信息:

    isSoftwareOnly()

    如果编解码器仅在软件中运行,则返回 true。软件编解码器并不能保证渲染性能。

    isHardwareAccelerated()

    如果编解码器由硬件加速,则返回 true。

    isVendor()

    如果编解码器由设备供应商提供,则返回 true;如果由 Android 平台提供,则返回 false。

    isAlias()

    MediaCodecList 可能针对使用备用编解码器名称(别名)的同一底层编解码器包含额外的条目。如果此条目中的编解码器是另一个编解码器的别名,则此方法会返回 true。

    此外,MediaCodec.getCanonicalName() 会针对通过别名创建的编解码器返回底层编解码器名称。

    性能点

    “性能点”表示编解码器以特定高度、宽度和帧速率渲染视频的能力。例如,UHD_60 性能点表示以每秒 60 帧的速度渲染超高清视频(3840x2160 像素)。

    方法 MediaCodecInfo.VideoCapabilities.getSupportedPerformancePoints() 会返回编解码器可以渲染或捕获的 PerformancePoint 条目列表。

    您可以通过调用 PerformancePoint.covers(PerformancePoint) 来检查给定的 PerformancePoint 是否会覆盖另一个性能点。例如,UHD_60.covers(UHD_50) 会返回 true。

    我们为所有硬件加速的编解码器都提供了性能点列表。如果编解码器连标准性能点的最低值都不能满足,则此列表可能为空。

    请注意,已升级到 Android Q 但未更新供应商映像的设备是没有性能点数据的,因为此数据来自供应商 HAL。在这种情况下,getSupportedPerformancePoints() 会返回 null。

    单色摄像头支持

    Android 9(API 等级 28)首次引入了单色摄像头功能。Android Q 为单色摄像头支持增加了几项增强功能:

    • 新增了对 Y8 流格式的支持,以提高内存效率。
    • 支持单色原始 DNG 捕获。
    • 引入了 MONO 和 NIR CFA 枚举,以区分常规单色摄像头和近红外摄像头。

    您可以使用此功能来捕捉原生单色图片。逻辑多摄像头设备可以使用单色摄像头作为物理子摄像头,以获取更出色的低光图片质量。

    动态深度格式

    从 Android Q 开始,摄像头可以使用名为“动态深度格式”(DDF) 的新架构将图片的深度数据存储在单独的文件中。应用可以请求 JPG 图片及其深度元数据,以便在后期处理中利用这些信息来应用所需的模糊处理,而无需修改原始图片数据。

    ANGLE

    在 Android Q 发布后,Android 开发者和合作伙伴可以选择使用 ANGLE 运行,这是 Chrome 组织中的一个项目,能够将 ES 置于 Vulkan 上层,而不必使用供应商提供的 ES 驱动程序。

    有关详情,请参阅 ANGLE

    无障碍服务 API

    Android Q 引入了以下新的无障碍服务功能和 API:

    AccessibilityNodeInfo 输入键标记

    在 Android Q 中,AccessibilityNodeInfo 得到了增强,现在增加了一个新标记,用于指示其是否代表文本输入键的。您可以使用 AccessibilityNodeInfo.isTextEntryKey() 方法来访问此标记。

    无障碍对话框语音反馈

    当无障碍服务要求用户重复按无障碍快捷方式以启动服务时,此对话框现在可以在服务请求时附带文字转语音提示。

    物理键盘的无障碍快捷方式

    在 Android Q 中,用户现在可以通过物理键盘来触发无障碍快捷方式,只需按 Control+Alt+Z 即可。

    软键盘控制器增强功能

    在 Android Q 中,无障碍服务现在可以请求显示软键盘,即使设备检测到连接了硬键盘也不例外。用户可以替换此行为。

    用户定义的无障碍服务超时

    Android Q 引入了 API 方法 AccessibilityManager.getRecommendedTimeoutMillis(),用于为用户针对互动式和非互动式的无障碍界面元素定义的超时提供支持。返回值受用户偏好设置和无障碍服务 API 的影响。

    自动填充方面的改进

    Android Q 包含对自动填充服务的以下改进。

    与兼容性相关的自动填充请求

    您现在可以使用 FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST 标记来确定是否通过兼容性模式生成了自动填充请求。

    同时保存用户名和密码

    您现在可以支持应用使用多个活动来显示用户名、密码和其他字段的情况,只需使用 SaveInfo.FLAG_DELAY_SAVE 标记即可。

    用户与保存界面的互动

    您现在可以在保存对话框中显示和隐藏密码字段,只需在此对话框中设置操作监听器,并更改相应密码远程视图的可见性即可。

    支持更新数据集

    现在,自动填充功能可以更新现有密码。例如,如果用户已经存储了一个密码,然后又保存了一个新密码,则自动填充功能现在会提示用户更新现有密码,而不是保存新密码。

    字段分类方面的改进

    Android Q 包含对 Field Classification API 的以下改进。

    UserData.Builder 构造函数

    UserData.Builder 构造函数已更改,以更好地符合 Builder 模式的要求。

    允许将一个值映射到多种类别 ID

    在使用 Android Q 中的 UserData.Builder 时,您现在可以将一个值映射到多种类别 ID。在以前的版本中,如果一个值被添加多次,系统就会抛出异常。

    改进了对信用卡号码的支持

    现在,字段分类可以检测四位数字作为信用卡号码的最后四位数字。

    支持特定于应用的字段分类

    Android Q 增加了 FillResponse.setUserData(),让您能够在会话期间设置特定于应用的用户数据。这有助于自动填充服务检测包含特定于应用的内容的字段的类型。

    界面和系统控件

    Android Q 提供以下界面方面的改进:

    支持 JVMTI PopFrame 功能

    Android Q 增加了对 Android JVMTI 实现中的 can_pop_frames 功能的支持。在调试时,此功能让您能够在断点暂停并调整函数的局部变量、全局变量或实现,然后重新运行函数。有关详情,请参阅 Oracle 的 Pop Frame 参考页面

    Surface Control API

    Android Q 提供了一个 SurfaceControl API,用于对系统合成器 (SurfaceFlinger) 进行底层访问。对于大多数用户而言,SurfaceView 是使用此合成器的正确方法。SurfaceControl API 在某些情况下很有用,例如:

    • 同步多个表面
    • 跨进程的表面嵌入
    • 底层生命周期管理

    SurfaceControl API 在 SDK 和 NDK 绑定中都可用。NDK 实现包含用于与合成器手动交换缓冲区的 API。这为遇到 BufferQueue 限制的用户提供了一种替代方案。

    WebView 挂起渲染程序检测

    Android Q 引入了一个新的 WebViewRendererClient 抽象类,应用可以使用它来检测 WebView 是否无响应。要使用此类,请执行以下操作:

    1. 定义自己的子类,并实现其 onRendererResponsive() 和 onRendererUnresponsive() 方法。
    2. 将 WebViewRendererClient 的实例附加到一个或多个 WebView 对象。
    3. 如果 WebView 无响应,则系统会调用客户端的 onRendererUnresponsive() 方法以传递 WebView 和 WebViewRenderer(如果 WebView 是单进程,则 WebViewRenderer 参数为 null)。您的应用可以执行适当的操作,例如向用户显示一个对话框,以询问其是否要暂停渲染流程。

    如果 WebView 保持无响应状态,则系统会定期调用 onRendererUnresponsive()(不超过每五秒钟一次),但不会执行任何其他操作。如果 WebView 再次无响应,系统只会调用 onRendererResponsive() 一次。

    设置面板

    Android Q 引入了“设置面板”,这是一种 API,让应用能够在自身环境中向用户显示设置。这可以避免用户转到设置更改 NFC 或移动数据等设置,以便使用此应用。

    1. 用户尝试在设备未连接到网络时打开网页。Chrome 弹出互联网连接设置面板…

    2. 用户可以开启 WLAN 并选择网络,而无需离开 Chrome 应用。

    例如,假设用户打开了网络浏览器,而其设备已开启飞行模式。在 Android Q 之前的版本中,此应用只能显示一条通用消息,要求用户打开设置以恢复连接。而借助 Android Q,浏览器应用便可以显示一个内嵌面板,其中会显示各种主要连接设置,例如飞行模式、WLAN(包括附近的网络)和移动数据。借助此面板,用户无需离开应用即可恢复连接。

    要显示设置面板,请使用以下其中一项新推出的 Settings.Panel 操作触发一个 Intent:

        val panelIntent = Intent(Settings.Panel.settings_panel_type)

        startActivityForResult(panelIntent)

       settings_panel_type 可以是下列项之一:

    • ACTION_INTERNET_CONNECTIVITY:显示与互联网连接相关的设置,例如飞行模式、WLAN 和移动数据。
    • ACTION_NFC:显示与近距离无线通信 (NFC) 相关的所有设置。
    • ACTION_VOLUME:显示所有音频流的音量设置,以及勿扰设置。

    我们计划针对此功能引入一个 AndroidX 封装容器。在搭载 Android 9(API 级别 28)或更低级别的设备上调用时,此封装容器会在设置应用中打开最合适的页面。

    共享功能方面的改进

    Android Q 为共享功能提供了多项改进。要了解所有详情,请参阅 Android Q 中共享功能方面的改进

    角色

    Android Q 引入了一种标准工具,即“角色”。借助此工具,操作系统可以根据易于理解的用例授予应用越权访问系统功能和用户数据的权限。在语义上,每个角色都代表着一个常见的用例,例如播放音乐、管理照片或发送短信。

    从测试版 1 开始,平台就为 Android Q 提供了一组预定义的角色。应用可以使用新的 RoleManager 类来查询可用角色并发出担任特定角色的请求。

    要详细了解角色如何影响 Android Q 中应用的行为,请参阅角色预览指南。

    Kotlin

    Android Q 对 Kotlin 开发进行了以下更新。

    libcore API 的可空性注释

    Android Q 改进了 SDK 中针对 libcore API 的可空性注释的覆盖范围。借助这些注释,在 Android Studio 中使用 Kotlin 或 Java 可空性分析的应用开发者可以在与这些 API 互动时获取非 Null 信息。

    通常,Kotlin 中的为空性合同违规行为会导致编译错误。为确保与现有代码兼容,所有新注释都仅限于 @RecentlyNullable 和 @RecentlyNonNull。这意味着为空性违规行为会引发警告,而不是错误。

    此外,Android 9 中添加的所有 @RecentlyNullable 或 @RecentlyNonNull 注释都会分别更改为 @Nullable和 @NonNull。这意味着为空性违规行为现在会引发错误,而不是警告。

    要详细了解注释方面的变更,请参阅 Android 开发者博客中的 Android Pie SDK 现已更适用于 Kotlin一文。

    NDK

    Android Q 包含以下 NDK 方面的变更。

    触发基于 Mallinfo 的垃圾回收

    当小型平台 Java 对象引用 C++ 堆中的大型对象时,通常只有在系统已回收并(举例而言)最终确定 Java 对象后,才能回收 C++ 对象。在之前的版本中,平台会估算与 Java 对象相关联的许多 C++ 对象的大小。这种估算并不总是准确,并且偶尔会导致内存使用量大大增加,因为平台无法在应该进行垃圾回收时完成回收。

    在 Android Q 中,垃圾回收器 (GC) 会跟踪系统 malloc() 分配的堆的总大小,以确保 malloc() 分配的大型堆始终包含在可触发 GC 的计算中。因此,与 Java 执行交错大量 C++ 分配的应用可能会出现垃圾回收频率提高的现象。其他应用的频率则可能会略有下降。

    改进了文件描述符所有权的调试

    Android Q 增加了 fdsan,它可以帮助您更轻松地查找和修复文件描述符所有权方面的问题。

    与错误处理文件描述符所有权相关的错误(通常表现为“use-after-close”和“double-close”)类似于内存分配“use-after-free”和“double-free”错误,但通常更难以诊断和修复。“fdsan”会尝试通过强制执行文件描述符所有权来检测和/或防止文件描述符误管理。

    要详细了解与这些问题相关的崩溃,请参阅 fdsan 检测到的错误。要详细了解 fdsan,请参阅关于 fdsan 的 Googlesource 页面

    展开全文
  • Android Q隐私权:权限变更 本文档介绍了权限模型的一些变更。这些变更有助于增强用户隐私。 其中一些变更会影响在Android Q上运行的所有应用,而其他变更仅会影响以Android Q为目标平台的应用。 影响所有应用的...

    Android Q隐私权:权限变更

    本文档介绍了权限模型的一些变更。这些变更有助于增强用户隐私。

    其中一些变更会影响在Android Q上运行的所有应用,而其他变更仅会影响以Android Q为目标平台的应用。

    影响所有应用的变更

    以下变更会影响在Android Q上运行的所有应用,即使这些应用以Android 9(API级别28)或更低版本为目标平台也是如此。

    限制对屏幕内容的访问

    为了保护用户的屏幕内容,Android Q更改了READ_FRAME_BUFFER,CAPTURE_VIDEO_OUTPUT和CAPTURE_SECURE_VIDEO_OUTPUT权限的范围,使其只能访问签名,从而禁止以静默方式访问设备的屏幕内容。

    需要访问设备屏幕内容的应用应用MediaProjectionAPI,此API会显示提示,要求用户同意声明。

    面向用户的权限检查(针对旧版应用)

    如果您的应用以Android 5.1(API级别22)或更低版本为目标平台,则当用户首次在Android Q上运行该应用时,他们会看到一个权限屏幕,如图1所示。此屏幕可以让用户撤消系统先前在安装时授予您应用的访问权限。

    注意:如果您要在Google Play上发布应用,则必须以Android 8.0(API级别26)或更高版本为目标平台。要了解详情,请参阅有关如何符合Google Play的目标API级别要求的指南。

    1.面向用户的对话框,允许查看旧版权限

    从界面中移除了权限组

    从Android Q开始,应用无法查询权限在界面中的分组方式。

    影响以Android Q为目标平台的应用的变更

    以下变更仅会影响以Android为目标平台的应用。

    身体活动识别

    Android Q针对需 要检测用户动作(例如步行,骑车或坐车)的应用引入了一个新的ACTIVITY_RECOGNITION运行时权限。此项权限旨在让用户了解设备传感器数据在“设置”中的使用方式。

    注意:Google Play服务中的一些库(例如,活动识别API)不会提供结果,除非用户已授予您的应用此项权限。

    展开全文
  • Android Q隐私:更改相机和连接 本文档描述了访问摄像机和连接信息的几个限制。这些更改有助于保护用户的隐私。 其中一些更改会影响在Android Q上运行的所有应用,而其他更改仅会影响针对Android Q的应用。 影响...

    Android Q隐私:更改相机和连接

    本文档描述了访问摄像机和连接信息的几个限制。这些更改有助于保护用户的隐私。

    其中一些更改会影响在Android Q上运行的所有应用,而其他更改仅会影响针对Android Q的应用。

    影响所有应用的变化

    以下更改会影响在Android Q上运行的所有应用,即使它们定位到Android 9(API级别28)或更低级别。

    访问所有相机信息需要获得许可

    Android Q会更改getCameraCharacteristics() 默认情况下该方法返回的信息范围 。特别是,您的应用必须具有CAMERA 权限才能访问此方法的返回值中包含的潜在特定于设备的元数据。

    如果您的应用没有CAMERA权限,则无法访问以下字段:

    • ANDROID_LENS_POSE_ROTATION
    • ANDROID_LENS_POSE_TRANSLATION
    • ANDROID_LENS_INTRINSIC_CALIBRATION
    • ANDROID_LENS_RADIAL_DISTORTION
    • ANDROID_LENS_POSE_REFERENCE
    • ANDROID_LENS_DISTORTION
    • ANDROID_LENS_INFO_HYPERFOCAL_DISTANCE
    • ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE
    • ANDROID_SENSOR_REFERENCE_ILLUMINANT1
    • ANDROID_SENSOR_REFERENCE_ILLUMINANT2
    • ANDROID_SENSOR_CALIBRATION_TRANSFORM1
    • ANDROID_SENSOR_CALIBRATION_TRANSFORM2
    • ANDROID_SENSOR_COLOR_TRANSFORM1
    • ANDROID_SENSOR_COLOR_TRANSFORM2
    • ANDROID_SENSOR_FORWARD_MATRIX1
    • ANDROID_SENSOR_FORWARD_MATRIX2

    启用和禁用Wi-Fi的限制

    在Android Q上运行的应用无法启用或停用Wi-Fi。该 WifiManager.setWifiEnabled()方法总是返回false。

    如果需要,请使用设置面板提示用户启用和禁用Wi-Fi。

    影响针对Android Q的应用的更改

    以下更改仅在针对Android Q时影响应用。

    Wi-Fi网络配置限制

    为了保护用户隐私,现在将Wi-Fi网络列表的手动配置限制在系统应用程序中。如果您的应用针对Android Q,则以下方法不再返回有用数据:

    • 该getConfiguredNetworks()方法始终返回一个空列表。
    • 返回一个整数值-每个网络操作方法addNetwork()和 updateNetwork()-always返回-1。
    • 每个网络操作,返回一个布尔值- ,removeNetwork(), reassociate(),enableNetwork(),disableNetwork(),reconnect()和 disconnect()-always回报false。

    如果您的应用需要连接到Wi-Fi网络,请使用以下备用方法:

    • 要触发与Wi-Fi网络的即时本地连接,请使用 WifiNetworkSpecifier标准NetworkRequest对象。
    • 要添加Wi-Fi网络以考虑为用户提供Internet访问,请使用WifiNetworkSuggestion对象。您可以分别通过调用WifiManager.addNetworkSuggestions()和 添加和删 除出现在自动连接网络选择对话框中的网络WifiManager.removeNetworkSuggestions()。这些方法不需要任何位置权限。

    电话,Wi-Fi,蓝牙API所需的精确位置许可

    除非您的应用具有此ACCESS_FINE_LOCATION权限,否则在Android Q上运行时,您的应用无法在Wi-Fi,Wi-Fi Aware或蓝牙API中使用多种方法。以下列表显示了受影响的方法。

    注意:如果您的应用在Android Q上运行但针对的是Android 9(API级别28)或更低级别,则只要您的应用具有ACCESS_COARSE_LOCATION或拥有该ACCESS_FINE_LOCATION权限,您就可以使用受影响的API 。

    电话

    • TelephonyManager
      • getCellLocation()
      • getAllCellInfo()
      • requestNetworkScan()
      • requestCellInfoUpdate()
      • getAvailableNetworks()
      • getServiceStateForSubscriber
      • getServiceState()
    • TelephonyScanManager
      • requestNetworkScan()
    • PhoneStateListener
      • onCellLocationChanged()
      • onCellInfoChanged()
      • onServiceStateChanged()
    • NetworkScanCallback
      • onResults()

    无线上网

    • WifiScanner
      • startScan()
    • WifiManager
      • startScan()
      • getScanResults()
      • getConnectionInfo()
      • getConfiguredNetworks()
    • WifiAwareManager
    • WifiP2pManager
    • WifiRTTManager

    蓝牙

    • BluetoothAdapter
      • startDiscovery()
      • startLeScan()
      • LeScanCallback()
    展开全文
  • Android Q 深色主题

    千次阅读 2019-10-23 21:36:21
    Android Q 深色主题 官方文档: https://developer.android.com/preview/features/darktheme Android Q 提供全新的深色主题背景,既会应用于 Android 系统界面,也会应用于设备上运行的应用。 深色主题背景具有诸多...
  • [AndroidQ] Q怎么重新开启Android Beam

    千次阅读 2019-08-09 16:06:51
    Android Q 移除Android Beam. Google将于Android Q开始移除Android Beam. Android Q如何重新开启Android Beam.
  • 其中一些变更会影响在 Android Q 上运行的所有应用,而其他变更仅会影响以 Android Q 为目标平台的应用。 影响所有应用的变更 以下变更会影响在 Android Q 上运行的所有应用,即使这些应用以 Android 9(API 级别 ...
  • Android设备唯一标识符(适配Android Q) 目录 Android设备唯一标识符(适配Android Q) 一、需求场景 二、Android设备信息 1、DeviceId(IMEI) 2、AndroidId 3、Serial Number 4、Wlan或者蓝牙的MAC地址...
  • Android Q 适配详细操作

    千次阅读 2019-08-21 17:12:20
    升级Android Q 的两种情况, 1.API在28以下的未适配Android X 的项目适配Android Q。 2.已经适配Android X 的被应用市场平台要求项目适配Android Q。 思路就是,先适配Android X(适配过的跳过),再从Android X ...
  • Android Q(10) 文件存储适配

    万次阅读 多人点赞 2019-09-29 18:05:03
    Android Q官方文档 Android Q 分区存储 Android Q文件存储机制修改成了沙盒模式,和IOS神似 应用只能访问自己沙盒下的文件和公共媒体文件 对于Android Q以下,还是使用老的文件存储方式 权限 Android Q不再需要...
  • Android Q 适配指南

    千次阅读 2019-11-18 16:37:27
    Android Q 适配 官方文档: https://developer.android.com/about/versions/10 在Android 10开始版本中,官方的改动较大,相应的开发者适配成本还是很高的。 这里按照2019.11.11 google android q workshop流程,...
  • Android Q (Android 10.0)

    万次阅读 2019-03-18 11:14:45
    通常,我们会在三月份的某个时候看到第一个开发... Android Q版,最大的亮点集中在隐私安全和智能交互两方面,其中在隐私安全方面Android Q增加了外部存储策略变更、位置权限的后台访问限制、后台应用(不限于摄像...
  • androidQ android10 获取Imei问题

    万次阅读 热门讨论 2019-09-17 23:58:20
    今天很多伙伴讨论 androidQ 之后不能获取Imei了,那我们换个位置思考,反正都是为了唯一标识 长话短说总结出两个方案 1.生成uuid存本地 不要存sp 就算卸载了也还在 2.用androidId来做唯一标识 String android_id ...
  • Android Q 不叫 Q,正式命名为 Android 10

    千次阅读 2019-08-24 15:28:04
    根据官方博文,谷歌已经公布了 Android Q 的名称,它并不像以前一样,以甜食命名,也不是以任何以字母 Q 开头来命名,而是简单称它为 Android 10。 该公司表示,正在改变其发布版本的命名方式。该公司写道: 这些...
  • Android Q分区存储权限变更及适配

    万次阅读 多人点赞 2019-06-30 11:15:46
    Android Q中引入了分区储存功能,在外部存储设备中为每个应用提供了一个“隔离存储沙盒”。其他应用无法直接访问应用的沙盒文件。由于文件是应用的私有文件,不再需要任何权限即可访问和保存自己的文件。此变更并...
  • Android Q访问公共外部存储受限

    万次阅读 2019-11-15 16:53:22
    Android Q(即 Android 10)开始,应用访问外部存储的私有目录(即Context.getExternalFilesDir())不需要申请READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE权限。同时,正常情况下,就算应用有申请READ_...
  • Android Q行为变化

    千次阅读 2019-03-29 14:50:49
    以下行为会影响所有运行在Android Q机型上的App,不管你的App指定的targetSdkVersion是不是Android Q。 非SDK接口限制。 WiFi直连广播。 以下两个与WiFi直连有关的广播不再是sticky模式 WIFI_P2P_...
  • 最近公司研发的 Android Qandroid 10)的5G手机,然后就开始适配Android Q;发现无系统权限的应用无法获取到可作为唯一标识的IMEI,MAC等等;由于我们的项目自己做了数据采集,项目中也接入了百度、头条、腾讯的...
  • Android Q 平台做了一些行为变更,这些变更可能会影响您的应用。以下行为变更仅影响以 Android Q 或更高版本为目标平台的应用。如果您的应用将targetSdkVersion设为“android-Q”或更高版本,则应修改您的应用以适当...
  • 适配Android Q拍照和读取相册图片

    万次阅读 2019-10-31 11:23:38
    Google发行Android Q版本也有很长一段时间了,华为应用市场已经要求要适配Android Q版本了,所以,我们也要去对Android Q进行适配。 先讲一下咱们这节用到的新特性 Android Q文件存储机制修改成了沙盒模式,类似于...
  • Android Q 正式命名为 Android 10

    千次阅读 2019-08-23 18:02:20
    根据官方博文,谷歌已经公布了 Android Q 的名称,它并不是想以前一样,以甜食命名,也不是以任何以字母 Q 开头来命名,而是简单称它为 Android 10。 该公司表示,它正在改变其发布版本的命名方式,以推动更大程度的...
  • Android Q(10) 拍照问题

    千次阅读 2020-02-05 15:17:18
    AndroidQ新增的分区存储 Android Q文件存储机制修改成了沙盒模式,和IOS神似 应用只能访问自己沙盒下的文件和公共媒体文件 对于Android Q以下,还是使用老的文件存储方式 权限变更 Android Q 更改了应用对...
  • Android Q的全新特性与隐私权限

    万次阅读 2019-05-10 23:50:53
    在前几天的Google I/O 2019大会上,发布了Android Q版本(Android 10)。Android Q带来了许多新特性,也增强安全隐私保护,包括支持折叠屏、非SDK接口限制、共享内存、分区存储、系统二进制文件映射到只执行内存、WLAN...
  • 最近公司相机升级到AndroidQ 遇到无法存储SD卡,原来是Goole抛弃了原有的API(不是过时,是抛弃,导致之前的存储API不能用)查看Google API 进行了代码更换, 在在更换过程中遇到了比较多的坑,将部分关键代码记录下来,...
  • AndroidQ(10)黑暗模式适配

    万次阅读 2019-09-03 10:59:12
    这不,今年的AndroidQ如期而至。这里简单介绍一下Android的新特性: AndroidQ全局暗黑模式 隐私权限的更新 AndroidQ新版的手势导航(其实就是仿IOS) 系统日程UI的优化(还有其他系统UI上的优化) Google组件...
  • Android Q沙盒机制 使用探究

    千次阅读 2019-09-03 16:16:07
    以下基于Android Q Bate3版本,最新版本为bate5 基本没有变化,另外target小于29,并且app没有手动开启沙盒模式,可以不用适配。 适配必看 1、权限有改动 Note: 早先Android Q版本的 READ_MEDIA_IMAGES, READ_...
  • Android 10(Android Q) 适配

    千次阅读 2019-09-23 11:50:51
    Android Q 中的隐私权-重大隐私权变更 官方网站-展示时间敏感的通知 1. 设备硬件信息读取限制 在Android10中, 系统不允许普通App请求android.permission.READ_PHONE_STATE权限, 故新版App需要取消该动态权限的...
  • 适配Android Q上读取多媒体文件

    千次阅读 2019-08-16 19:08:14
    Android Q版本出来也有一段时间了,但是大部分我们都没有去适配过它,首选说一下Android Q版,最大的亮点集中在隐私安全和智能交互两方面,其中在隐私安全方面Android Q增加了外部存储策略变更、位置权限的后台访问...
  • PictureSelector 2.0 Android Q 适配之旅。

    千次阅读 热门讨论 2020-01-08 15:37:07
    PictureSelector 至从2016年12月底提交第一个版本以来时至今日总共也已提交80...由于Google Android Q正式版预计在2019年8月份前后推出,所以针对AndroidQ的适配已经迫在眉睫了,其中Android Q一项比较重要的变更就...
  • androidQ系统新特性

    千次阅读 2019-05-15 16:15:13
    目录Android Q是什么Android Q发布日期Android Q新的特性1.黑暗模式2.桌面模式3.隐私增强4.超级锁定模式5.屏幕录制6.移除 Android Beam7.运营商锁定8.面部识别9.不允许从后台读取剪贴板信息10.降级应用程序更新11.新...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 82,846
精华内容 33,138
关键字:

androidq