这事实在是太惭愧了.
国庆假期前一周, 有客户反映安卓端app选择图片打开相册加载很慢.
这个问题涉及第三方easyphoto, 看起来又是性能问题, 这两个都是我很害怕的.
于是我上来就预设, 这个问题我很可能改不好, 这是个超级大难题, 需要很长时间, 最终也未必有结果.
我自然认为是那个第三方不行, 得用系统自带的选择图片功能才行, 于是投身于安卓 intent 方式选择图片的研究中.
中间遇到了 intent 方式选择图片后的路径问题, 便研究起安卓文件系统来, 发现把选择到的视频存储到应用的专属目录即可绕过路径问题.
但 intent 方式只能单独选择图片, 或者单独选择视频, 这样的话用户会觉得很不适应, 因为以前都是视频和图片可以一起选的.
我便和领导反映了这个问题.
领导则让我定位一下 easyphoto 这个第三方选图片时, 究竟哪里慢.
我这才隐约意识到, 我从头到尾根本就没有去定位问题具体在什么位置.
于是今天早上来了便开始看 easyphoto 的代码, 根据文件名判断功能, 然后进去看具体方法, 找到了这个方法, 然后运行发现这个方法很慢.
private synchronized void initAlbum(Context context) {
album.clear();
long now = System.currentTimeMillis();
if (Setting.selectedPhotos.size() > Setting.count) {
throw new RuntimeException("AlbumBuilder: 默认勾选的图片张数不能大于设置的选择数!" + "|默认勾选图片张数:" + Setting.selectedPhotos.size() + "|设置的选择数:" + Setting.count);
}
boolean canReadWidth =
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN;
// boolean isQ = android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.Q;
final String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC";
Uri contentUri;
String selection = null;
String[] selectionAllArgs = null;
if (Setting.isOnlyVideo()) {
contentUri = MediaStore.Video.Media.getContentUri("external");
} else if (!Setting.showVideo) {
contentUri = MediaStore.Images.Media.getContentUri("external");
} else {
contentUri = MediaStore.Files.getContentUri("external");
selection =
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)";
selectionAllArgs =
new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)};
}
ContentResolver contentResolver = context.getContentResolver();
List<String> projectionList = new ArrayList<String>();
projectionList.add(MediaStore.Files.FileColumns._ID);
// if (isQ) {
// projectionList.add(MediaStore.MediaColumns.RELATIVE_PATH);
// }else {
projectionList.add(MediaStore.MediaColumns.DATA);
// }
projectionList.add(MediaStore.MediaColumns.DISPLAY_NAME);
projectionList.add(MediaStore.MediaColumns.DATE_MODIFIED);
projectionList.add(MediaStore.MediaColumns.MIME_TYPE);
projectionList.add(MediaStore.MediaColumns.SIZE);
projectionList.add(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
if (!Setting.useWidth) {
if (Setting.minWidth != 1 && Setting.minHeight != 1)
Setting.useWidth = true;
}
if (canReadWidth) {
if (Setting.useWidth) {
projectionList.add(MediaStore.MediaColumns.WIDTH);
projectionList.add(MediaStore.MediaColumns.HEIGHT);
if (!Setting.isOnlyVideo())
projectionList.add(MediaStore.MediaColumns.ORIENTATION);
}
}
if (Setting.showVideo) {
projectionList.add(MediaStore.MediaColumns.DURATION);
}
projections = projectionList.toArray(new String[0]);
Cursor cursor = contentResolver.query(contentUri, projections, selection,
selectionAllArgs, sortOrder);
if (cursor == null) {
// Log.d(TAG, "call: " + "Empty photos");
} else if (cursor.moveToFirst()) {
String albumItem_all_name = getAllAlbumName(context);
String albumItem_video_name =
context.getString(R.string.selector_folder_video_easy_photos);
int albumNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
int durationCol = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION);
int WidthCol = 0;
int HeightCol = 0;
int orientationCol = -1;
if (canReadWidth && Setting.useWidth) {
WidthCol = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH);
HeightCol = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT);
orientationCol = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION);
}
boolean hasTime = durationCol > 0;
do {
long id = cursor.getLong(0);
String path = cursor.getString(1);
String name = cursor.getString(2);
long dateTime = cursor.getLong(3);
String type = cursor.getString(4);
long size = cursor.getLong(5);
long duration = 0;
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(type)) {
continue;
}
if (size < Setting.minSize) {
continue;
}
boolean isVideo = type.contains(Type.VIDEO);// 是否是视频
int width = 0;
int height = 0;
int orientation = 0;
if (isVideo) {
if (hasTime)
duration = cursor.getLong(durationCol);
if (duration <= Setting.videoMinSecond || duration >= Setting.videoMaxSecond) {
continue;
}
} else {
if (orientationCol != -1) {
orientation = cursor.getInt(orientationCol);
}
if (!Setting.showGif) {
if (path.endsWith(Type.GIF) || type.endsWith(Type.GIF)) {
continue;
}
}
if (Setting.useWidth) {
if (canReadWidth) {
width = cursor.getInt(WidthCol);
height = cursor.getInt(HeightCol);
}
if (width == 0 || height == 0) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
width = options.outWidth;
height = options.outHeight;
}
if (orientation == 90 || orientation == 270) {
int temp = width;
width = height;
height = temp;
}
if (width < Setting.minWidth || height < Setting.minHeight) {
continue;
}
}
}
Uri uri = ContentUris.withAppendedId(isVideo ?
MediaStore.Video.Media.getContentUri("external") :
MediaStore.Images.Media.getContentUri("external"), id);
//某些机型,特定情况下三方应用或用户操作删除媒体文件时,没有通知媒体库,导致媒体库表中还有其数据,但真实文件已经不存在
File file = new File(path);
if (!file.isFile()) {
continue;
}
Photo imageItem = new Photo(name, uri, path, dateTime, width, height, orientation
, size,
duration, type);
if (!Setting.selectedPhotos.isEmpty()) {
int selectSize = Setting.selectedPhotos.size();
for (int i = 0; i < selectSize; i++) {
Photo selectedPhoto = Setting.selectedPhotos.get(i);
if (path.equals(selectedPhoto.path)) {
imageItem.selectedOriginal = Setting.selectedOriginal;
Result.addPhoto(imageItem);
}
}
}
// 初始化“全部”专辑
if (album.isEmpty()) {
// 用第一个图片作为专辑的封面
album.addAlbumItem(albumItem_all_name, "", path, uri);
}
// 把图片全部放进“全部”专辑
album.getAlbumItem(albumItem_all_name).addImageItem(imageItem);
if (Setting.showVideo && isVideo && !albumItem_video_name.equals(albumItem_all_name)) {
album.addAlbumItem(albumItem_video_name, "", path, uri);
album.getAlbumItem(albumItem_video_name).addImageItem(imageItem);
}
// 添加当前图片的专辑到专辑模型实体中
String albumName;
String folderPath;
if (albumNameCol > 0) {
albumName = cursor.getString(albumNameCol);
folderPath = albumName;
} else {
File parentFile = new File(path).getParentFile();
if (null == parentFile) {
continue;
}
folderPath = parentFile.getAbsolutePath();
albumName = StringUtils.getLastPathSegment(folderPath);
}
album.addAlbumItem(albumName, folderPath, path, uri);
album.getAlbumItem(albumName).addImageItem(imageItem);
} while (cursor.moveToNext() && canRun);
cursor.close();
if (!Setting.selectedPhotos.isEmpty() && Setting.isSequentialSelectedPhotos) {
int selectSize = Setting.selectedPhotos.size();
int photoSize = Result.photos.size();
ArrayList<Photo> tempList = new ArrayList<>(photoSize);
for (int i = 0; i < selectSize; i++) {
for (int j = 0; j < photoSize; j++) {
if (Result.photos.get(j).path.equals(Setting.selectedPhotos.get(i).path)){
tempList.add(i,Result.photos.get(j));
}
}
}
Result.photos = tempList;
}
}
Log.d(TAG, "initAlbum: " + (System.currentTimeMillis() - now));
}
这是个超长的方法, 代码很多, 我又一次预设这玩意我看不懂, 就算看懂了, 它这是一下子把图片加载进来, 改为分批加载很难, 就算改好了怎么分批显示也好难.
总之, 我全程都在觉得难, 还想了很多合理的原因. 就是没去定位问题出在哪?
我定位到这个方法, 却没再仔细定位这么多代码中, 究竟哪一句导致的加载过慢.
这时候我又跟领导反映这个问题一时半会解决不了.
领导也没说什么, 直接过来看了一会, 打了断点一步步调试下去, 发现是 do while 循环遍历这块比较慢, 那问题就在于这个循环吗?
如果是我, 肯定就又认为, 循环这玩意慢没法解决啊.
然后领导看了循环里的代码, 找到了这样一段代码,
//某些机型,特定情况下三方应用或用户操作删除媒体文件时,没有通知媒体库,导致媒体库表中还有其数据,但真实文件已经不存在
File file = new File(path);
if (!file.isFile()) {
continue;
}
这块会不会导致慢, 这就不是我能看出来的, 可能打断点也试不出来, 因为这段代码不循环的话应该也不慢.
领导之所以能判断是这段导致的慢, 是因为他知道导致速度慢的原因一般就两个, 一个是网络, 一个是磁盘读写.
然后领导把这段代码注释掉, 速度一下子提高了80%.
至此, 问题解决.
虽然领导没说什么, 我真实羞愧的无地自容, 我至少应该定位到这个循环, 再去找领导支持的. 结果东冲西撞绕来绕去, 还牢骚满腹, 问题还丝毫没得到解决.
以后遇到问题, 一定要在代码层面, 具体到某行某段代码层面, 定位到问题的起因.
还有, 不要心里预设问题太难. 这样的自我限制会导致什么也做不成.
另外, 那段代码之所以可以注释掉, 是因为绝大多数情况下, 不会出现那种情况.