Skip to content

Commit e21ec2e

Browse files
author
Tianhua Ran
committed
refactor: speed up maintaining database
1 parent 5d40c26 commit e21ec2e

File tree

2 files changed

+151
-99
lines changed

2 files changed

+151
-99
lines changed

library/src/main/java/com/liulishuo/filedownloader/database/SqliteDatabaseImpl.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,21 @@
3535

3636
/**
3737
* Persist data to SQLite database.
38-
*
38+
* <p>
3939
* You can valid this database implementation through:
4040
* <p>
4141
* class MyApplication extends Application {
4242
* ...
4343
* public void onCreate() {
44-
* ...
45-
* FileDownloader.setupOnApplicationOnCreate(this)
46-
* .database(SqliteDatabaseImpl.createMaker())
47-
* ...
48-
* .commit();
49-
* ...
44+
* ...
45+
* FileDownloader.setupOnApplicationOnCreate(this)
46+
* .database(SqliteDatabaseImpl.createMaker())
47+
* ...
48+
* .commit();
49+
* ...
5050
* }
5151
* ...
5252
* }
53-
*
5453
*/
5554
public class SqliteDatabaseImpl implements FileDownloadDatabase {
5655

@@ -267,6 +266,7 @@ private void update(final int id, final ContentValues cv) {
267266
public class Maintainer implements FileDownloadDatabase.Maintainer {
268267

269268
private final SparseArray<FileDownloadModel> needChangeIdList = new SparseArray<>();
269+
private final SparseArray<FileDownloadModel> needRemoveList = new SparseArray<>();
270270
private MaintainerIterator currentIterator;
271271

272272
private final SparseArray<FileDownloadModel> downloaderModelMap;
@@ -317,6 +317,14 @@ public void onFinishMaintain() {
317317
}
318318
}
319319

320+
// remove invalid
321+
final int removeSize = needRemoveList.size();
322+
for (int i = 0; i < removeSize; i++) {
323+
final int modelId = needRemoveList.keyAt(i);
324+
db.delete(TABLE_NAME, FileDownloadModel.ID + " = ?",
325+
new String[]{String.valueOf(modelId)});
326+
}
327+
320328
// initial cache of connection model
321329
if (downloaderModelMap != null && connectionModelListMap != null) {
322330
final int size = downloaderModelMap.size();
@@ -339,16 +347,25 @@ public void onFinishMaintain() {
339347

340348
@Override
341349
public void onRemovedInvalidData(FileDownloadModel model) {
350+
synchronized (needRemoveList) {
351+
needRemoveList.put(model.getId(), model);
352+
}
342353
}
343354

344355
@Override
345356
public void onRefreshedValidData(FileDownloadModel model) {
346-
if (downloaderModelMap != null) downloaderModelMap.put(model.getId(), model);
357+
if (downloaderModelMap != null) {
358+
synchronized (downloaderModelMap) {
359+
downloaderModelMap.put(model.getId(), model);
360+
}
361+
}
347362
}
348363

349364
@Override
350365
public void changeFileDownloadModelId(int oldId, FileDownloadModel modelWithNewId) {
351-
needChangeIdList.put(oldId, modelWithNewId);
366+
synchronized (needChangeIdList) {
367+
needChangeIdList.put(oldId, modelWithNewId);
368+
}
352369
}
353370

354371
}

library/src/main/java/com/liulishuo/filedownloader/download/CustomComponentHolder.java

Lines changed: 124 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@
2323
import com.liulishuo.filedownloader.services.DownloadMgrInitialParams;
2424
import com.liulishuo.filedownloader.services.ForegroundServiceConfig;
2525
import com.liulishuo.filedownloader.stream.FileDownloadOutputStream;
26+
import com.liulishuo.filedownloader.util.FileDownloadExecutors;
2627
import com.liulishuo.filedownloader.util.FileDownloadHelper;
2728
import com.liulishuo.filedownloader.util.FileDownloadLog;
2829
import com.liulishuo.filedownloader.util.FileDownloadUtils;
2930

3031
import java.io.File;
3132
import java.io.IOException;
33+
import java.util.ArrayList;
3234
import java.util.Iterator;
35+
import java.util.List;
36+
import java.util.concurrent.Future;
37+
import java.util.concurrent.ThreadPoolExecutor;
38+
import java.util.concurrent.atomic.AtomicInteger;
3339

3440
/**
3541
* The holder for supported custom components.
@@ -168,120 +174,149 @@ private DownloadMgrInitialParams getDownloadMgrInitialParams() {
168174
return initialParams;
169175
}
170176

171-
private static void maintainDatabase(FileDownloadDatabase.Maintainer maintainer) {
177+
private static void maintainDatabase(final FileDownloadDatabase.Maintainer maintainer) {
172178
final Iterator<FileDownloadModel> iterator = maintainer.iterator();
173-
long refreshDataCount = 0;
174-
long removedDataCount = 0;
175-
long resetIdCount = 0;
179+
final AtomicInteger removedDataCount = new AtomicInteger(0);
180+
final AtomicInteger resetIdCount = new AtomicInteger(0);
181+
final AtomicInteger refreshDataCount = new AtomicInteger(0);
176182
final FileDownloadHelper.IdGenerator idGenerator = getImpl().getIdGeneratorInstance();
177183

178184
final long startTimestamp = System.currentTimeMillis();
185+
final List<Future> futures = new ArrayList<>();
186+
final ThreadPoolExecutor maintainThreadPool = FileDownloadExecutors.newDefaultThreadPool(3,
187+
FileDownloadUtils.getThreadPoolName("MaintainDatabase"));
179188
try {
180189
while (iterator.hasNext()) {
181-
boolean isInvalid = false;
182190
final FileDownloadModel model = iterator.next();
183-
do {
184-
if (model.getStatus() == FileDownloadStatus.progress
185-
|| model.getStatus() == FileDownloadStatus.connected
186-
|| model.getStatus() == FileDownloadStatus.error
187-
|| (model.getStatus() == FileDownloadStatus.pending && model
188-
.getSoFar() > 0)
191+
final Future modelFuture = maintainThreadPool.submit(new Runnable() {
192+
@Override
193+
public void run() {
194+
boolean isInvalid = false;
195+
do {
196+
if (model.getStatus() == FileDownloadStatus.progress
197+
|| model.getStatus() == FileDownloadStatus.connected
198+
|| model.getStatus() == FileDownloadStatus.error
199+
|| (model.getStatus() == FileDownloadStatus.pending && model
200+
.getSoFar() > 0)
189201
) {
190-
// Ensure can be covered by RESUME FROM BREAKPOINT.
191-
model.setStatus(FileDownloadStatus.paused);
192-
}
193-
final String targetFilePath = model.getTargetFilePath();
194-
if (targetFilePath == null) {
195-
// no target file path, can't used to resume from breakpoint.
196-
isInvalid = true;
197-
break;
198-
}
199-
200-
final File targetFile = new File(targetFilePath);
201-
// consider check in new thread, but SQLite lock | file lock aways effect, so
202-
// sync
203-
if (model.getStatus() == FileDownloadStatus.paused
204-
&& FileDownloadUtils.isBreakpointAvailable(model.getId(), model,
205-
model.getPath(), null)) {
206-
// can be reused in the old mechanism(no-temp-file).
207-
208-
final File tempFile = new File(model.getTempFilePath());
202+
// Ensure can be covered by RESUME FROM BREAKPOINT.
203+
model.setStatus(FileDownloadStatus.paused);
204+
}
205+
final String targetFilePath = model.getTargetFilePath();
206+
if (targetFilePath == null) {
207+
// no target file path, can't used to resume from breakpoint.
208+
isInvalid = true;
209+
break;
210+
}
209211

210-
if (!tempFile.exists() && targetFile.exists()) {
211-
final boolean successRename = targetFile.renameTo(tempFile);
212-
if (FileDownloadLog.NEED_LOG) {
213-
FileDownloadLog.d(FileDownloadDatabase.class,
214-
"resume from the old no-temp-file architecture "
215-
+ "[%B], [%s]->[%s]",
216-
successRename, targetFile.getPath(), tempFile.getPath());
212+
final File targetFile = new File(targetFilePath);
213+
// consider check in new thread, but SQLite lock | file lock aways
214+
// effect, so sync
215+
if (model.getStatus() == FileDownloadStatus.paused
216+
&& FileDownloadUtils.isBreakpointAvailable(model.getId(), model,
217+
model.getPath(), null)) {
218+
// can be reused in the old mechanism(no-temp-file).
219+
220+
final File tempFile = new File(model.getTempFilePath());
221+
222+
if (!tempFile.exists() && targetFile.exists()) {
223+
final boolean successRename = targetFile.renameTo(tempFile);
224+
if (FileDownloadLog.NEED_LOG) {
225+
FileDownloadLog.d(FileDownloadDatabase.class,
226+
"resume from the old no-temp-file architecture "
227+
+ "[%B], [%s]->[%s]",
228+
successRename, targetFile.getPath(),
229+
tempFile.getPath());
230+
}
231+
}
232+
}
217233

234+
/**
235+
* Remove {@code model} from DB if it can't used for judging whether the
236+
* old-downloaded file is valid for reused & it can't used for resuming
237+
* from BREAKPOINT, In other words, {@code model} is no use anymore for
238+
* FileDownloader.
239+
*/
240+
if (model.getStatus() == FileDownloadStatus.pending
241+
&& model.getSoFar() <= 0) {
242+
// This model is redundant.
243+
isInvalid = true;
244+
break;
218245
}
219-
}
220-
}
221246

222-
/**
223-
* Remove {@code model} from DB if it can't used for judging whether the
224-
* old-downloaded file is valid for reused & it can't used for resuming from
225-
* BREAKPOINT, In other words, {@code model} is no use anymore for
226-
* FileDownloader.
227-
*/
228-
if (model.getStatus() == FileDownloadStatus.pending && model.getSoFar() <= 0) {
229-
// This model is redundant.
230-
isInvalid = true;
231-
break;
232-
}
247+
if (!FileDownloadUtils.isBreakpointAvailable(model.getId(), model)) {
248+
// It can't used to resuming from breakpoint.
249+
isInvalid = true;
250+
break;
251+
}
233252

234-
if (!FileDownloadUtils.isBreakpointAvailable(model.getId(), model)) {
235-
// It can't used to resuming from breakpoint.
236-
isInvalid = true;
237-
break;
238-
}
253+
if (targetFile.exists()) {
254+
// It has already completed downloading.
255+
isInvalid = true;
256+
break;
257+
}
239258

240-
if (targetFile.exists()) {
241-
// It has already completed downloading.
242-
isInvalid = true;
243-
break;
244-
}
259+
} while (false);
260+
261+
262+
if (isInvalid) {
263+
maintainer.onRemovedInvalidData(model);
264+
removedDataCount.addAndGet(1);
265+
} else {
266+
final int oldId = model.getId();
267+
final int newId = idGenerator.transOldId(oldId, model.getUrl(),
268+
model.getPath(), model.isPathAsDirectory());
269+
if (newId != oldId) {
270+
if (FileDownloadLog.NEED_LOG) {
271+
FileDownloadLog.d(FileDownloadDatabase.class,
272+
"the id is changed on restoring from db:"
273+
+ " old[%d] -> new[%d]",
274+
oldId, newId);
275+
}
276+
model.setId(newId);
277+
maintainer.changeFileDownloadModelId(oldId, model);
278+
resetIdCount.addAndGet(1);
279+
}
245280

246-
} while (false);
247-
248-
249-
if (isInvalid) {
250-
iterator.remove();
251-
maintainer.onRemovedInvalidData(model);
252-
removedDataCount++;
253-
} else {
254-
final int oldId = model.getId();
255-
final int newId = idGenerator.transOldId(oldId, model.getUrl(), model.getPath(),
256-
model.isPathAsDirectory());
257-
if (newId != oldId) {
258-
if (FileDownloadLog.NEED_LOG) {
259-
FileDownloadLog.d(FileDownloadDatabase.class,
260-
"the id is changed on restoring from db:"
261-
+ " old[%d] -> new[%d]",
262-
oldId, newId);
281+
maintainer.onRefreshedValidData(model);
282+
refreshDataCount.addAndGet(1);
263283
}
264-
model.setId(newId);
265-
maintainer.changeFileDownloadModelId(oldId, model);
266-
resetIdCount++;
267284
}
268-
269-
maintainer.onRefreshedValidData(model);
270-
refreshDataCount++;
271-
}
285+
});
286+
futures.add(modelFuture);
272287
}
273288

274289
} finally {
275-
FileDownloadUtils.markConverted(FileDownloadHelper.getAppContext());
276-
maintainer.onFinishMaintain();
277-
// 566 data consumes about 140ms
290+
final Future markConvertedFuture = maintainThreadPool.submit(new Runnable() {
291+
@Override
292+
public void run() {
293+
FileDownloadUtils.markConverted(FileDownloadHelper.getAppContext());
294+
}
295+
});
296+
futures.add(markConvertedFuture);
297+
final Future finishMaintainFuture = maintainThreadPool.submit(new Runnable() {
298+
@Override
299+
public void run() {
300+
maintainer.onFinishMaintain();
301+
}
302+
});
303+
futures.add(finishMaintainFuture);
304+
for (Future future : futures) {
305+
try {
306+
if (!future.isDone()) future.get();
307+
} catch (Exception e) {
308+
if (FileDownloadLog.NEED_LOG) FileDownloadLog
309+
.e(FileDownloadDatabase.class, e, e.getMessage());
310+
}
311+
}
312+
futures.clear();
278313
if (FileDownloadLog.NEED_LOG) {
279314
FileDownloadLog.d(FileDownloadDatabase.class,
280315
"refreshed data count: %d , delete data count: %d, reset id count:"
281316
+ " %d. consume %d",
282-
refreshDataCount, removedDataCount, resetIdCount,
317+
refreshDataCount.get(), removedDataCount.get(), resetIdCount.get(),
283318
System.currentTimeMillis() - startTimestamp);
284319
}
285320
}
286321
}
287-
}
322+
}

0 commit comments

Comments
 (0)