最近整理了一下图片懒加载的代码,顺便也把接受项目里面的文件管理整理了一下,去掉了一些代码,留下了一个下载功能还有缓存机制,正好可以配合前面图片懒加载使用,下面看代码:
public class FileManager {
//网络下载的缓存清除时间——15days
private static final long AUTO_CLR_TIME = 15 * 24 * 3600 * 1000;
//缓存 BUFFER 大小
public static final int BUFFER_SIZE = 1024;
//application 上下文
@SuppressWarnings("FieldCanBeLocal")
private final Context mContext;
//缓存路径
private final File mCachePath;
//下载文件路径
private final File mFilesPath;
//线程池
private final ExecutorService threadPool = Executors.newFixedThreadPool(8);
//相同链接的锁,这里用LinkedHashMap限制一下储存的数量
private static final Map<String, Semaphore> mUrlLockMap =
new LinkedHashMap<String, Semaphore>() {
protected boolean removeEldestEntry(Entry<String, Semaphore> eldest) {
return size() >= 64 * 0.75;
}
};
//避免内存泄漏,使用Application的Context
@SuppressLint("StaticFieldLeak")
private static FileManager manager = null;
public static synchronized FileManager getInstance() {
//双重校验
if (manager == null) {
synchronized (FileManager.class) {
if (manager == null) {
manager = new FileManager(getGobalApplication());
}
}
}
return manager;
}
private FileManager(Context applicationContext) {
mContext = applicationContext;
if (isExternalStorageAvailable()) {
//外部储存
mCachePath = mContext.getExternalCacheDir();
mFilesPath = mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
} else {
//应用内部储存,可能被清除
mCachePath = mContext.getCacheDir();
mFilesPath = mContext.getFilesDir();
}
//自动清除过期缓存数据
autoClearCache();
}
//是否有外部储存
private boolean isExternalStorageAvailable() {
return !Environment.isExternalStorageRemovable()
|| Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
//清除过期缓存文件
private void autoClearCache() {
synchronized (FileManager.class) {
long curTime = System.currentTimeMillis();
File[] files = mCachePath.listFiles();
if (null != files) {
for (File file : files) {
if (file.isFile() && curTime - file.lastModified() >= AUTO_CLR_TIME ) {
file.delete();
}
}
}
}
}
//获取缓存文件总大小
public long getCacheSize() {
long size = 0;
File[] files = mCachePath.listFiles();
if (null != files) {
for (File file : files) {
if (file.isFile()) {
size += file.length();
}
}
}
return size;
}
//获取文件目录总大小
public long getFilesSize() {
long size = 0;
File[] files = mFilesPath.listFiles();
if (null != files) {
for (File file : files) {
if (file.isFile()) {
size += file.length();
}
}
}
return size;
}
//获取缓存文件
public File getCacheFile(String url) {
return new File(mCachePath,makeMD5(url) + getSuffixOfUrl(url));
}
//获取链接md5作为缓存文件名
private String makeMD5(String in) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bytes = in.getBytes(StandardCharsets.UTF_8);
if (null != md5 && null != bytes) {
md5.update(bytes);
StringBuilder hexValue = new StringBuilder();
for (byte val : bytes) {
hexValue.append(String.format("%02x", val));
}
return hexValue.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
//从链接总获取文件后缀名
private static String getSuffixOfUrl(String url) {
if (!TextUtils.isEmpty(url)) {
String[] split = url.split("\\.");
if (split.length > 0) {
return "." + split[split.length - 1];
}
}
return "";
}
//获取文件
public File getFile(String filename) {
synchronized (FileManager.class) {
return new File(mFilesPath, filename);
}
}
//下载文件
public void download(String url, OnProgressListener listener) {
threadPool.execute(()-> {
//空路径
if (TextUtils.isEmpty(url)) {
listener.onFinish(null);
}
//本地路径
File file = new File(url);
if (file.exists()) {
listener.onFinish(file);
}
//同时下载文件会对同一个文件做修改,需要使用锁机制,使用信号量简单点
Semaphore semaphore;
synchronized (mUrlLockMap) {
semaphore = mUrlLockMap.get(url);
if (null == semaphore) {
semaphore = new Semaphore(1);
mUrlLockMap.put(url, semaphore);
}
}
//保证锁一定解锁
try {
semaphore.acquire();
//再检查是否已下载
file = getFile(url);
if (file.exists()) {
listener.onFinish(file);
}
//网络下载部分
HttpURLConnection conn = null;
BufferedInputStream inputStream = null;
FileOutputStream outputStream = null;
RandomAccessFile randomAccessFile;
File cacheFile = null;
//要下载文件大小
long remoteFileSize = 0, sum = 0;
byte[] buffer = new byte[BUFFER_SIZE];
try {
URL conUrl = new URL(url);
conn = (HttpURLConnection) conUrl.openConnection();
remoteFileSize = Long.parseLong(conn.getHeaderField("Content-Length"));
//获取缓存文件
cacheFile = getCacheFile(url);
existsCase:
if (cacheFile.exists()) {
long cacheFileSize = cacheFile.length();
//缓存已经下载完成
if (cacheFileSize == remoteFileSize) {
break existsCase;
} else if (cacheFileSize > remoteFileSize) {
//多线程同时修改文件可导致次情况
FileWriter fileWriter = new FileWriter(cacheFile);
fileWriter.write("");
fileWriter.flush();
fileWriter.close();
cacheFileSize = 0;
}
conn.disconnect(); // must reconnect
conn = (HttpURLConnection) conUrl.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
conn.setRequestProperty("User-Agent", "VcareCity");
conn.setRequestProperty("RANGE", "buffer=" + cacheFileSize + "-");
conn.setRequestProperty("Accept",
"image/gif,image/x-xbitmap,application/msword,*/*");
//随机访问
randomAccessFile = new RandomAccessFile(cacheFile, "rw");
randomAccessFile.seek(cacheFileSize);
inputStream = new BufferedInputStream(conn.getInputStream());
//继续写入文件
int size;
sum = cacheFileSize;
while ((size = inputStream.read(buffer)) > 0) {
randomAccessFile.write(buffer, 0, size);
sum += size;
if (listener != null) {
listener.onProgress((int) (sum * 100 / remoteFileSize));
}
}
randomAccessFile.close();
} else {
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
cacheFile.createNewFile();
inputStream = new BufferedInputStream(conn.getInputStream());
outputStream = new FileOutputStream(cacheFile);
int size;
while ((size = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, size);
sum += size;
if (listener != null) {
listener.onProgress((int) (sum * 100 / remoteFileSize));
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != conn) conn.disconnect();
if (null != inputStream) inputStream.close();
if (null != outputStream) outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (sum > 0 && sum == remoteFileSize && null != cacheFile) {
//复制文件到指定目录
cacheFile.renameTo(file);
cacheFile.delete();
if (listener != null) {
listener.onFinish(file);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
public interface OnProgressListener {
void onProgress(int progress);
void onFinish(File file);
}
}
简单说明
两类目录
if (isExternalStorageAvailable()) {
//外部储存
mCachePath = mContext.getExternalCacheDir();
mFilesPath = mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
} else {
//应用内部储存,可能被清除
mCachePath = mContext.getCacheDir();
mFilesPath = mContext.getFilesDir();
}
这里设置了两种文件目录,一个用来缓存,一个用来保存文件,具体这些目录如何获取可以直接看官方文档,另附一篇博客文章:
官方文档
https://developer.android.google.cn/training/data-storage
Android 存储使用参考
https://www.liaohuqiu.net/cn/posts/storage-in-android/
自动清除缓存
//private static final long AUTO_CLR_TIME = 15 * 24 * 3600 * 1000;
//清除过期缓存文件
private void autoClearCache() {
synchronized (FileManager.class) {
long curTime = System.currentTimeMillis();
File[] files = mCachePath.listFiles();
if (null != files) {
for (File file : files) {
if (file.isFile() && curTime - file.lastModified() >= AUTO_CLR_TIME ) {
file.delete();
}
}
}
}
}
在获取 FileManager 单例的时候,会根据设置的时间间隔清除过期文件。
下载文件
下载的文件具体文件放在 mFilesPath,而缓存文件放在 mCachePath,缓存文件根据链接 md5 值命名,当使用链接下载时先通过 mFilesPath 查找文件,如果有即之前下载成功过,如果没就查 mCachePath 内的缓存文件,并判断缓存文件的大小,实现继续下载。
至于锁机制,就是给一个链接一个信号量,获取到信号量的线程才能够下载,当获取到信号量时还应该检查一下在等待过程中是否有文件下载完成了。
结语
内容不多,功能也挺鸡肋,可以通过一些稳定的库实现,如果想简单使用的话还是可以的!
end