Reduce the number of cache files

- Increase the default cache file size to 5MB
- Recommended a minimum cache file size of 2MB to discourage
  applications from specifying values small enough such that
  unreasonably large numbers of cache files are generated
- Allow maxCacheFileSize=C.LENGTH_UNSET, equivalent to setting it
  to MAX_VALUE. This is just for API consistency with other APIs
  we have that accept LENGTH_UNSET

Issue: #4253
PiperOrigin-RevId: 227524233
This commit is contained in:
olly 2019-01-02 16:21:09 +00:00 committed by Oliver Woodman
parent ae65bcecd4
commit 0ed7ddecd7
6 changed files with 45 additions and 50 deletions

View File

@ -23,6 +23,7 @@ import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.upstream.FileDataSourceFactory; import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
import com.google.android.exoplayer2.upstream.PriorityDataSourceFactory; import com.google.android.exoplayer2.upstream.PriorityDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.Cache; import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
import com.google.android.exoplayer2.upstream.cache.CacheDataSinkFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSinkFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
@ -111,7 +112,7 @@ public final class DownloaderConstructorHelper {
DataSink.Factory writeDataSinkFactory = DataSink.Factory writeDataSinkFactory =
cacheWriteDataSinkFactory != null cacheWriteDataSinkFactory != null
? cacheWriteDataSinkFactory ? cacheWriteDataSinkFactory
: new CacheDataSinkFactory(cache, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); : new CacheDataSinkFactory(cache, CacheDataSink.DEFAULT_MAX_CACHE_FILE_SIZE);
onlineCacheDataSourceFactory = onlineCacheDataSourceFactory =
new CacheDataSourceFactory( new CacheDataSourceFactory(
cache, cache,

View File

@ -20,6 +20,7 @@ import com.google.android.exoplayer2.upstream.DataSink;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException; import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ReusableBufferedOutputStream; import com.google.android.exoplayer2.util.ReusableBufferedOutputStream;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.File; import java.io.File;
@ -36,8 +37,13 @@ import java.io.OutputStream;
*/ */
public final class CacheDataSink implements DataSink { public final class CacheDataSink implements DataSink {
/** Default {@code maxCacheFileSize} recommended for caching use cases. */
public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 5 * 1024 * 1024;
/** Default buffer size in bytes. */ /** Default buffer size in bytes. */
public static final int DEFAULT_BUFFER_SIZE = 20480; public static final int DEFAULT_BUFFER_SIZE = 20 * 1024;
private static final long MIN_RECOMMENDED_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024;
private static final String TAG = "CacheDataSink";
private final Cache cache; private final Cache cache;
private final long maxCacheFileSize; private final long maxCacheFileSize;
@ -64,12 +70,15 @@ public final class CacheDataSink implements DataSink {
} }
/** /**
* Constructs a CacheDataSink using the {@link #DEFAULT_BUFFER_SIZE}. * Constructs an instance using {@link #DEFAULT_BUFFER_SIZE}.
* *
* @param cache The cache into which data should be written. * @param cache The cache into which data should be written.
* @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for a * @param maxCacheFileSize The maximum size of a cache file, in bytes. If a request results in
* {@link DataSpec} whose size exceeds this value, then the data will be fragmented into * data being written whose size exceeds this value, then the data will be fragmented into
* multiple cache files. * multiple cache files. If set to {@link C#LENGTH_UNSET} then no fragmentation will occur.
* Using a small value allows for finer-grained cache eviction policies, at the cost of
* increased overhead both on the cache implementation and the file system. Values under
* {@code (2 * 1024 * 1024)} are not recommended.
*/ */
public CacheDataSink(Cache cache, long maxCacheFileSize) { public CacheDataSink(Cache cache, long maxCacheFileSize) {
this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE); this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE);
@ -77,15 +86,29 @@ public final class CacheDataSink implements DataSink {
/** /**
* @param cache The cache into which data should be written. * @param cache The cache into which data should be written.
* @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for a * @param maxCacheFileSize The maximum size of a cache file, in bytes. If a request results in
* {@link DataSpec} whose size exceeds this value, then the data will be fragmented into * data being written whose size exceeds this value, then the data will be fragmented into
* multiple cache files. * multiple cache files. If set to {@link C#LENGTH_UNSET} then no fragmentation will occur.
* Using a small value allows for finer-grained cache eviction policies, at the cost of
* increased overhead both on the cache implementation and the file system. Values under
* {@code (2 * 1024 * 1024)} are not recommended.
* @param bufferSize The buffer size in bytes for writing to a cache file. A zero or negative * @param bufferSize The buffer size in bytes for writing to a cache file. A zero or negative
* value disables buffering. * value disables buffering.
*/ */
public CacheDataSink(Cache cache, long maxCacheFileSize, int bufferSize) { public CacheDataSink(Cache cache, long maxCacheFileSize, int bufferSize) {
Assertions.checkState(
maxCacheFileSize > 0 || maxCacheFileSize == C.LENGTH_UNSET,
"maxCacheFileSize must be positive or C.LENGTH_UNSET.");
if (maxCacheFileSize != C.LENGTH_UNSET
&& maxCacheFileSize < MIN_RECOMMENDED_MAX_CACHE_FILE_SIZE) {
Log.w(
TAG,
"maxCacheFileSize is below the minimum recommended value of "
+ MIN_RECOMMENDED_MAX_CACHE_FILE_SIZE
+ ". This may cause poor cache performance.");
}
this.cache = Assertions.checkNotNull(cache); this.cache = Assertions.checkNotNull(cache);
this.maxCacheFileSize = maxCacheFileSize; this.maxCacheFileSize = maxCacheFileSize == C.LENGTH_UNSET ? Long.MAX_VALUE : maxCacheFileSize;
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
syncFileDescriptor = true; syncFileDescriptor = true;
} }

View File

@ -46,5 +46,4 @@ public final class CacheDataSinkFactory implements DataSink.Factory {
public DataSink createDataSink() { public DataSink createDataSink() {
return new CacheDataSink(cache, maxCacheFileSize, bufferSize); return new CacheDataSink(cache, maxCacheFileSize, bufferSize);
} }
} }

View File

@ -45,14 +45,6 @@ import java.util.Map;
*/ */
public final class CacheDataSource implements DataSource { public final class CacheDataSource implements DataSource {
/**
* Default maximum single cache file size.
*
* @see #CacheDataSource(Cache, DataSource, int)
* @see #CacheDataSource(Cache, DataSource, int, long)
*/
public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024;
/** /**
* Flags controlling the CacheDataSource's behavior. Possible flag values are {@link * Flags controlling the CacheDataSource's behavior. Possible flag values are {@link
* #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link * #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link
@ -163,7 +155,7 @@ public final class CacheDataSource implements DataSource {
* @param upstream A {@link DataSource} for reading data not in the cache. * @param upstream A {@link DataSource} for reading data not in the cache.
*/ */
public CacheDataSource(Cache cache, DataSource upstream) { public CacheDataSource(Cache cache, DataSource upstream) {
this(cache, upstream, 0, DEFAULT_MAX_CACHE_FILE_SIZE); this(cache, upstream, /* flags= */ 0);
} }
/** /**
@ -176,29 +168,11 @@ public final class CacheDataSource implements DataSource {
* and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0.
*/ */
public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) { public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) {
this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE);
}
/**
* Constructs an instance with default {@link DataSource} and {@link DataSink} instances for
* reading and writing the cache. The sink is configured to fragment data such that no single
* cache file is greater than maxCacheFileSize bytes.
*
* @param cache The cache.
* @param upstream A {@link DataSource} for reading data not in the cache.
* @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR}
* and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0.
* @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size
* exceeds this value, then the data will be fragmented into multiple cache files. The
* finer-grained this is the finer-grained the eviction policy can be.
*/
public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags,
long maxCacheFileSize) {
this( this(
cache, cache,
upstream, upstream,
new FileDataSource(), new FileDataSource(),
new CacheDataSink(cache, maxCacheFileSize), new CacheDataSink(cache, CacheDataSink.DEFAULT_MAX_CACHE_FILE_SIZE),
flags, flags,
/* eventListener= */ null); /* eventListener= */ null);
} }

View File

@ -46,20 +46,11 @@ public final class CacheDataSourceFactory implements DataSource.Factory {
/** @see CacheDataSource#CacheDataSource(Cache, DataSource, int) */ /** @see CacheDataSource#CacheDataSource(Cache, DataSource, int) */
public CacheDataSourceFactory( public CacheDataSourceFactory(
Cache cache, DataSource.Factory upstreamFactory, @CacheDataSource.Flags int flags) { Cache cache, DataSource.Factory upstreamFactory, @CacheDataSource.Flags int flags) {
this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE);
}
/** @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long) */
public CacheDataSourceFactory(
Cache cache,
DataSource.Factory upstreamFactory,
@CacheDataSource.Flags int flags,
long maxCacheFileSize) {
this( this(
cache, cache,
upstreamFactory, upstreamFactory,
new FileDataSourceFactory(), new FileDataSourceFactory(),
new CacheDataSinkFactory(cache, maxCacheFileSize), new CacheDataSinkFactory(cache, CacheDataSink.DEFAULT_MAX_CACHE_FILE_SIZE),
flags, flags,
/* eventListener= */ null); /* eventListener= */ null);
} }

View File

@ -27,6 +27,7 @@ import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters; import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
@ -341,7 +342,13 @@ public final class CacheUtilTest {
cache, cache,
/* cacheKeyFactory= */ null, /* cacheKeyFactory= */ null,
// Set maxCacheFileSize to 10 to make sure there are multiple spans. // Set maxCacheFileSize to 10 to make sure there are multiple spans.
new CacheDataSource(cache, dataSource, 0, 10), new CacheDataSource(
cache,
dataSource,
new FileDataSource(),
new CacheDataSink(cache, /* maxCacheFileSize= */ 10),
/* flags= */ 0,
/* eventListener= */ null),
new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES], new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES],
/* priorityTaskManager= */ null, /* priorityTaskManager= */ null,
/* priority= */ 0, /* priority= */ 0,