最近在做一个TV项目时遇到了一个小问题:
主要是这样一种业务,一个主列表,主要是显示video文件的名称,右边一个列表显示详细的信息,包括文件名称、存储路径、视频时长、文件大小等等信息。左边的主列表切换不同的Item时,右边的信息对应变化。
在我使用常规的方式编写列表的时候出现了卡顿的问题,最终发现原因并解决了问题。
- 卡顿的原因
- 优化方法
- 最终效果
卡顿的原因
网上关于卡顿原因的资料很多。总结一下主要是:
1.未使用复用机制;
2.Item的布局过于复杂,或者嵌套层数太深;
3.不合适的调用notifyDataSetChanged()方法;
4.列表数据中包含耗时操作,并且未使用工作线程;
5.其他原因。
我的业务中出现卡顿的原因就是第四点。
因为列表中的视频文件的时长、文件的大小等属于耗时操作。其他几种原因也一并总结一下。
优化方法
第一、二种原因造成的问题,改进方法很直接,在Adapter中使用复用机制、将Item的嵌套层数减少,不能减少的尽可能使用<include/>标签
复用机制一般是使用ViewHolder(不论是普通的AdapterView还是现在流行的RecyclerView)第三种原因造成的现象主要是要注意更新列表的时候需要注意:
1) 创建Adapter的时候,传入的数据可能是List<T> == null 或者 List<T>.size()==0;
2) 数据可能需要读取本地、网络文件(比较耗费时间),读取的过程需要再工作线程中进行,可以自己管理Thread,也可以使用线程池、或者网络框架比如Retrofit等(内部包含了线程管理)
3) 在完成读取数据之后,通知主线程更新UI,此时应该在收到更新UI的消息后,进行notifyDataSetChanged()的调用。
4) 在列表数据发生变化,一般包括用户新增一条数据、用户删除一条数据(一般对于一个用户可编辑的列表),操作完成也需要进行进行notifyDataSetChanged()的调用。
5) 其他非必要情况,尽可能不要调用notifyDataSetChanged()。耗时操作 :文件信息的获取:
/*简单的写一个线程池执行*/
mInfoExecutors.execute(new Runnable() {
@Override
public void run() {
try {
// 1.File的length方法得到文件大小(耗时)
long fileBytes = itemFile.length();
// 2.将文件大小转化为用户可读的内容(..KB、..MB、..GB)
String mSize = calculateFileSize(fileBytes);
// 3.文件最后一次修改的时间
long lastTime = itemFile.lastModified();
// 4.在未播放的情况下,获取视频文件的时长
long duration = getMediaFileDuration(itemFile.getPath());
Message.obtain(mInfoHandler, MSG_REFRESH_INFO, mInfoText).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
}
}
});
/**
* @param bytes 文件的长度
* @return 用户可读的文件大小
*/
private String calculateFileSize(long bytes) {
String result = "0 KB";
if(bytes > 0 && bytes < 1024*1024) {
result = String.format("%.1f", ((double)bytes/1024.0)) + "KB";
} else if (bytes > 1024*1024 && bytes < 1024*1024*1024) {
result = String.format("%.1f", ((double)bytes/1024/1024)) + "MB";
} else if (bytes > 1024*1024*1024) {
result = String.format("%.1f", ((double)bytes/1024/1024/1024)) + "GB";
}
return result;
}
/**
* @param filePath 视频文件的路径
* @return 视频文件的时长
*/
public static long getMediaFileDuration(String filePath){
long duration = 0L;
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
try {
if (filePath != null) {
mmr.setDataSource(filePath);
}
duration = Long.valueOf(mmr.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_DURATION));
} catch (Exception e) {
e.printStackTrace();
} finally {
mmr.release();
}
return duration;
}
5. 其他原因,需要根据业务逻辑和具体情况分析。