/* * Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.xiaopan.sketch.feature; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.locks.ReentrantLock; import me.xiaopan.sketch.Configuration; import me.xiaopan.sketch.Identifier; import me.xiaopan.sketch.SLogType; import me.xiaopan.sketch.SLog; import me.xiaopan.sketch.Sketch; import me.xiaopan.sketch.cache.BitmapPool; import me.xiaopan.sketch.cache.BitmapPoolUtils; import me.xiaopan.sketch.cache.DiskCache; import me.xiaopan.sketch.request.ImageFrom; import me.xiaopan.sketch.request.LoadRequest; import me.xiaopan.sketch.request.UriScheme; import me.xiaopan.sketch.util.DiskLruCache; import me.xiaopan.sketch.util.SketchUtils; /** * 图片预处理器,可读取APK文件的图标以及根据包名和版本号读取已安装APP的图标 */ public class ImagePreprocessor implements Identifier { private static final String INSTALLED_APP_URI_HOST = "installedApp"; private static final String INSTALLED_APP_URI_PARAM_PACKAGE_NAME = "packageName"; private static final String INSTALLED_APP_URI_PARAM_VERSION_CODE = "versionCode"; protected String logName = "ImagePreprocessor"; public static String createInstalledAppIconUri(String packageName, int versionCode) { return SketchUtils.concat( UriScheme.FILE.getSecondaryUriPrefix(), INSTALLED_APP_URI_HOST, "?", INSTALLED_APP_URI_PARAM_PACKAGE_NAME, "=", packageName, "&", INSTALLED_APP_URI_PARAM_VERSION_CODE, "=", versionCode); } public boolean isSpecific(LoadRequest loadRequest) { return isApkFile(loadRequest) || isInstalledApp(loadRequest); } public PreProcessResult process(LoadRequest loadRequest) { if (isApkFile(loadRequest)) { return getApkIconDiskCache(loadRequest); } if (isInstalledApp(loadRequest)) { return getInstalledAppIconDiskCache(loadRequest); } return null; } @Override public String getKey() { return logName; } private boolean isApkFile(LoadRequest loadRequest) { return loadRequest.getUriScheme() == UriScheme.FILE && SketchUtils.checkSuffix(loadRequest.getRealUri(), ".apk"); } private boolean isInstalledApp(LoadRequest loadRequest) { return loadRequest.getUriScheme() == UriScheme.FILE && loadRequest.getRealUri().startsWith(INSTALLED_APP_URI_HOST); } /** * 获取APK图标的缓存 */ private PreProcessResult getApkIconDiskCache(LoadRequest loadRequest) { String realUri = loadRequest.getRealUri(); Configuration configuration = loadRequest.getConfiguration(); File apkFile = new File(realUri); if (!apkFile.exists()) { return null; } long lastModifyTime = apkFile.lastModified(); String diskCacheKey = realUri + "." + lastModifyTime; DiskCache diskCache = configuration.getDiskCache(); ReentrantLock diskCacheEditLock = diskCache.getEditLock(diskCacheKey); diskCacheEditLock.lock(); PreProcessResult result = readApkIcon(configuration.getContext(), diskCache, loadRequest, diskCacheKey, realUri); diskCacheEditLock.unlock(); return result; } private PreProcessResult readApkIcon(Context context, DiskCache diskCache, LoadRequest loadRequest, String diskCacheKey, String realUri) { DiskCache.Entry apkIconDiskCacheEntry = diskCache.get(diskCacheKey); if (apkIconDiskCacheEntry != null) { return new PreProcessResult(apkIconDiskCacheEntry, ImageFrom.DISK_CACHE); } BitmapPool bitmapPool = Sketch.with(context).getConfiguration().getBitmapPool(); boolean lowQualityImage = loadRequest.getOptions().isLowQualityImage(); Bitmap iconBitmap = SketchUtils.readApkIcon(context, realUri, lowQualityImage, logName, bitmapPool); if (iconBitmap == null) { return null; } if (iconBitmap.isRecycled()) { if (SLogType.REQUEST.isEnabled()) { SLog.w(SLogType.REQUEST, logName, "apk icon bitmap recycled. %s", loadRequest.getKey()); } return null; } DiskCache.Editor diskCacheEditor = diskCache.edit(diskCacheKey); OutputStream outputStream; if (diskCacheEditor != null) { try { outputStream = new BufferedOutputStream(diskCacheEditor.newOutputStream(), 8 * 1024); } catch (IOException e) { e.printStackTrace(); BitmapPoolUtils.freeBitmapToPool(iconBitmap, bitmapPool); diskCacheEditor.abort(); return null; } } else { outputStream = new ByteArrayOutputStream(); } try { iconBitmap.compress(SketchUtils.bitmapConfigToCompressFormat(iconBitmap.getConfig()), 100, outputStream); if (diskCacheEditor != null) { diskCacheEditor.commit(); } } catch (DiskLruCache.EditorChangedException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } catch (IOException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } catch (DiskLruCache.ClosedException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } catch (DiskLruCache.FileNotExistException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } finally { BitmapPoolUtils.freeBitmapToPool(iconBitmap, bitmapPool); SketchUtils.close(outputStream); } if (diskCacheEditor != null) { apkIconDiskCacheEntry = diskCache.get(diskCacheKey); if (apkIconDiskCacheEntry != null) { return new PreProcessResult(apkIconDiskCacheEntry, ImageFrom.LOCAL); } else { if (SLogType.REQUEST.isEnabled()) { SLog.w(SLogType.REQUEST, logName, "not found apk icon cache file. %s", loadRequest.getKey()); } return null; } } else { return new PreProcessResult(((ByteArrayOutputStream) outputStream).toByteArray(), ImageFrom.LOCAL); } } /** * 获取已安装APP图标的缓存 */ private PreProcessResult getInstalledAppIconDiskCache(LoadRequest loadRequest) { String diskCacheKey = loadRequest.getUri(); Configuration configuration = loadRequest.getConfiguration(); DiskCache diskCache = configuration.getDiskCache(); ReentrantLock diskCacheEditLock = diskCache.getEditLock(diskCacheKey); diskCacheEditLock.lock(); PreProcessResult result = readInstalledAppIcon(configuration.getContext(), diskCache, loadRequest, diskCacheKey); diskCacheEditLock.unlock(); return result; } private PreProcessResult readInstalledAppIcon(Context context, DiskCache diskCache, LoadRequest loadRequest, String diskCacheKey) { DiskCache.Entry appIconDiskCacheEntry = diskCache.get(diskCacheKey); if (appIconDiskCacheEntry != null) { return new PreProcessResult(appIconDiskCacheEntry, ImageFrom.DISK_CACHE); } Uri uri = Uri.parse(loadRequest.getUri()); String packageName = uri.getQueryParameter(INSTALLED_APP_URI_PARAM_PACKAGE_NAME); int versionCode = Integer.valueOf(uri.getQueryParameter(INSTALLED_APP_URI_PARAM_VERSION_CODE)); PackageInfo packageInfo; try { packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return null; } if (packageInfo.versionCode != versionCode) { return null; } String apkFilePath = packageInfo.applicationInfo.sourceDir; BitmapPool bitmapPool = Sketch.with(context).getConfiguration().getBitmapPool(); boolean lowQualityImage = loadRequest.getOptions().isLowQualityImage(); Bitmap iconBitmap = SketchUtils.readApkIcon(context, apkFilePath, lowQualityImage, logName, bitmapPool); if (iconBitmap == null) { return null; } if (iconBitmap.isRecycled()) { if (SLogType.REQUEST.isEnabled()) { SLog.w(SLogType.REQUEST, logName, "apk icon bitmap recycled. %s", loadRequest.getKey()); } return null; } DiskCache.Editor diskCacheEditor = diskCache.edit(diskCacheKey); OutputStream outputStream; if (diskCacheEditor != null) { try { outputStream = new BufferedOutputStream(diskCacheEditor.newOutputStream(), 8 * 1024); } catch (IOException e) { e.printStackTrace(); BitmapPoolUtils.freeBitmapToPool(iconBitmap, bitmapPool); diskCacheEditor.abort(); return null; } } else { outputStream = new ByteArrayOutputStream(); } try { iconBitmap.compress(SketchUtils.bitmapConfigToCompressFormat(iconBitmap.getConfig()), 100, outputStream); if (diskCacheEditor != null) { diskCacheEditor.commit(); } } catch (DiskLruCache.EditorChangedException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } catch (IOException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } catch (DiskLruCache.ClosedException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } catch (DiskLruCache.FileNotExistException e) { e.printStackTrace(); diskCacheEditor.abort(); return null; } finally { BitmapPoolUtils.freeBitmapToPool(iconBitmap, bitmapPool); SketchUtils.close(outputStream); } if (diskCacheEditor != null) { appIconDiskCacheEntry = diskCache.get(diskCacheKey); if (appIconDiskCacheEntry != null) { return new PreProcessResult(appIconDiskCacheEntry, ImageFrom.LOCAL); } else { if (SLogType.REQUEST.isEnabled()) { SLog.w(SLogType.REQUEST, logName, "not found apk icon cache file. %s", loadRequest.getKey()); } return null; } } else { return new PreProcessResult(((ByteArrayOutputStream) outputStream).toByteArray(), ImageFrom.LOCAL); } } }