Android TechAndroid nougat

Android N系列适配---FileProvider

2016-10-31  本文已影响3422人  25a58172fbb5

Android N系列适配---FileProvider

Android 7.0的适配,主要包含方面:


Android 7.0 功能diff---详细介绍Android7.0拥有的功能

  1. 多窗口支持:
    1. 用户可以一次在屏幕上打开两个应用,或者处于分屏模式时一个应用位于另一个应用之上。 用户可以通过拖动两个应用之间的分隔线来调整应用。
    2. 在 Android TV 设备上,应用可以将自身置于画中画模式,从而让它们可以在用户浏览或与其他应用交互时继续显示内容。
    3. 可以指定app Activity大小,防止用户调整到该尺寸以下
  2. 通知增强功能:
    1. 模板更新
      1. 少量代码调整,即可使用新的通知模版开发
    2. 消息样式更新
      1. MessageStyle 类,可配置消息,会话标题,以及内容视图
    3. 捆绑通知
      1. 系统可以将消息按一定规律给组合,如消息主题,用户可以适当的进行Dismiss和Archive等操作
    4. 直接回复
      1. 即时通讯应用,支持用户直接在通知界面中快速回复消息
    5. 自定义视图
      1. 两个新的 API,使用自定义视图时可以充分利用系统装饰元素,如通知标题和操作
  3. Project Svelte 后台优化:
    1. 删除了三个常用隐式广播,继续扩展 JobScheduler 和 GCMNetworkManager
  4. apk signature scheme V2
    1. 新的应用签名方案
    2. Android Studio 2.2 和 Android Gradle 2.2 插件会使用 APK
  5. 附上官方链接:
    https://developer.android.com/about/versions/nougat/android-7.0.html#multi-window_support

行为变更和影响
  1. 当设备处于低电耗,首先会限制,关闭应用网络访问,推迟作业和同步,一定时间后,会对除去PowerManager.WakeLock和Alarmmanager闹铃,GPS和WIFI扫描以外的进行低电耗限制
  2. 后台优化,删除了三个隐式广播,如果app用到了,需要及时的解除关系
  3. 应用间共享文件的修改
  4. 无障碍改进,屏幕缩放,设置向导中视觉设置
  5. 附上官方链接:
    https://developer.android.com/about/versions/nougat/android-7.0-changes.html

Android 7.0 FileProvider的适配


Android 7.0对三方工具的影响

UIL(Universal-Image-Loader)为例

关于imageloader适配,加载了本地图片,竟然没有问题

    final ImageView imageView = (ImageView) LayoutInflater.from(context).inflate(R.layout.view_banner, null);
   String imageUri = "/mnt/sdcard/image.png";
   ImageLoader.getInstance().displayImage("file://"+imageUri, imageView);
  1. 如果想找到为何没有影响,需要读imageloader源码,直接从imageloader中的加载图片displayImage方法入手

     public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
         ImageLoadingListener listener, ImageLoadingProgressListener progressListener) 
    
  2. 找到bmp != null && !bmp.isRecycled()判断,如果没有从本地找到或者被回收掉了的话,直接走LoadAndDisplayImageTask,去加载图片

     Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
     if (bmp != null && !bmp.isRecycled()) {
         L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
    
         if (options.shouldPostProcess()) {
             ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                     options, listener, progressListener, engine.getLockForUri(uri));
             ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                     defineHandler(options));
             if (options.isSyncLoading()) {
                 displayTask.run();
             } else {
                 engine.submit(displayTask);
             }
         } else {
             options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
             listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
         }
     } else {
         if (options.shouldShowImageOnLoading()) {
             imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
         } else if (options.isResetViewBeforeLoading()) {
             imageAware.setImageDrawable(null);
         }
    
         ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                 options, listener, progressListener, engine.getLockForUri(uri));
         LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                 defineHandler(options));
         if (options.isSyncLoading()) {
             displayTask.run();
         } else {
             engine.submit(displayTask);
         }
     }
    
  3. 在LoadAndDisplayImageTask的run方法中,会判断是否bitmap为空,这样的话,就会尝试load Bitmap

     if (bmp == null || bmp.isRecycled()) {
             bmp = tryLoadBitmap();
    
  4. 这里才是加载图片的关键,首先去判断磁盘是否存在图片,如果存在,则直接从磁盘加载图片,如果本地没有,则取网络获取图片。

     private Bitmap tryLoadBitmap() throws TaskCancelledException {
     Bitmap bitmap = null;
     try {
         File imageFile = configuration.diskCache.get(uri);
         if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
             L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
             loadedFrom = LoadedFrom.DISC_CACHE;
    
             checkTaskNotActual();
             bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
         }
         if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
             L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
             loadedFrom = LoadedFrom.NETWORK;
    
             String imageUriForDecoding = uri;
             if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
                 imageFile = configuration.diskCache.get(uri);
                 if (imageFile != null) {
                     imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
                 }
             }
    
             checkTaskNotActual();
             bitmap = decodeImage(imageUriForDecoding);
    
             if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
                 fireFailEvent(FailType.DECODING_ERROR, null);
             }
         }
     } catch (IllegalStateException e) {
         fireFailEvent(FailType.NETWORK_DENIED, null);
     } catch (TaskCancelledException e) {
         throw e;
     } catch (IOException e) {
         L.e(e);
         fireFailEvent(FailType.IO_ERROR, e);
     } catch (OutOfMemoryError e) {
         L.e(e);
         fireFailEvent(FailType.OUT_OF_MEMORY, e);
     } catch (Throwable e) {
         L.e(e);
         fireFailEvent(FailType.UNKNOWN, e);
     }
     return bitmap;
     }
    
  5. 首次进入肯定是bitmap是空的,找到tryCacheImageOnDisk方法

     private boolean tryCacheImageOnDisk() throws TaskCancelledException {
         L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
    
         boolean loaded;
         try {
             loaded = downloadImage();
             if (loaded) {
                 int width = configuration.maxImageWidthForDiskCache;
                 int height = configuration.maxImageHeightForDiskCache;
                 if (width > 0 || height > 0) {
                     L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
                     resizeAndSaveImage(width, height); // TODO : process boolean result
                 }
             }
         } catch (IOException e) {
             L.e(e);
             loaded = false;
         }
         return loaded;
         }
    
  6. 里面清晰的可以看见,有个downloadImage方法

     private boolean downloadImage() throws IOException {
         InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
         if (is == null) {
             L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
             return false;
         } else {
             try {
                 return configuration.diskCache.save(uri, is, this);
             } finally {
                 IoUtils.closeSilently(is);
             }
         }
     }
    
  7. downloadImage方法中,获取到了一个Downloader,通过uri获取流

  8. 看看downloader是啥,有个子类BaseImageDownloader,看里面的getStream方法

     public InputStream getStream(String imageUri, Object extra) throws IOException {
         switch (Scheme.ofUri(imageUri)) {
             case HTTP:
             case HTTPS:
                 return getStreamFromNetwork(imageUri, extra);
             case FILE:
                 return getStreamFromFile(imageUri, extra);
             case CONTENT:
                 return getStreamFromContent(imageUri, extra);
             case ASSETS:
                 return getStreamFromAssets(imageUri, extra);
             case DRAWABLE:
                 return getStreamFromDrawable(imageUri, extra);
             case UNKNOWN:
             default:
                 return getStreamFromOtherSource(imageUri, extra);
         }
     }
    
  9. 那么问题就来了,我们传入的是file://前缀,会最终到downloader中获取stream,继续看看getStreamFromFile

     protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
         String filePath = Scheme.FILE.crop(imageUri);
         if (isVideoFileUri(imageUri)) {
             return getVideoThumbnailStream(filePath);
         } else {
             BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
             return new ContentLengthInputStream(imageStream, (int) new File(filePath).length());
         }
     }
    
  10. 显而易见,crop方法有问题

    public String crop(String uri) {
            if (!belongsTo(uri)) {
                throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme));
            }
            return uri.substring(uriPrefix.length());
        }
    
  11. uri.substring,有点意思,从uriPrefix的长度开始截取

    Scheme(String scheme) {
            this.scheme = scheme;
            uriPrefix = scheme + "://";
        }
    
  12. 这样就很明白了,UIL这个框架,直接从"file:// "往后,把具体的地址截取出来了,而且它直接用后面的地址获取到了InputStream,这样就可以避免7.0这个file://需要换成content://的问题,而避免了使用FileProvider。

  13. 最后附上一个没问题的例子。

    FileInputStream fileInputStream = new FileInputStream("/storage/emulated/0/Download/com.***.apk");
上一篇 下一篇

猜你喜欢

热点阅读