GDownload is a simple, powerful, easy to use, customizable file download client library for Android.
- Simple and easy to use API.
- Continuous downloading in the background.
- Concurrent downloading support.
- Ability to pause and resume downloads.
- Set the priority of a download.
- Network-specific downloading support.
- Ability to retry failed downloads.
- Ability to group downloads.
- Easy progress and status tracking.
- Download remaining time reporting (ETA).
- Download speed reporting.
- Download Elapsed time reporting.
- Save and Retrieve download information anytime.
- Scope Storage support.
- And more...
If you are saving downloads outside of your application's sandbox, you will need to add the following storage permissions to your application's manifest. For Android SDK version 23(M) and above, you will also need to explicitly request these permissions from the user.
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
Also, as you are going to use Internet to download files. We need to add the Internet access permissions in the Manifest.
<uses-permission android:name="android.permission.INTERNET"/>
Using GDownload is easy! Just add the following to your application's root build.gradle file.
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
And then add the dependency to the module level build.gradle file.
implementation 'com.github.tanoDxyz:GDownload:1.2'
Although! library can be used independently without initalization but initalization gives us two benefits.
First, it will reuse the existing downloaders from the pool if available.
Second, if the app is closed and the user wants to stop all the downloads running this will help out.
GDownload.init(lifecycle) //activity.lifecycle or any other lifecycle // could be null too
The above line will initalize the library and if the non null
Lifecycle
is passed the library will terminate all the running or paused downloads when
the lifecycle is destroyed.
Make sure you called GDownload.init(Lifecycle)
and then
Inside Activity
or Fragment
call the following method.
Remember if the argument passed is Activity
or Fragment
.
DownloadProgressListener
callbacks will be called only when the Activity
or Fragment
is visible.
GDownload.singleDownload(Activity or Fragment or Context) {
url = "url"
name = "fileName or FilePath"
downloadProgressListener = object :DownloadProgressListener {
override fun onConnectionEstablished(downloadInfo: DownloadInfo?) {}
override fun onDownloadProgress(downloadInfo: DownloadInfo?) {}
override fun onDownloadFailed(downloadInfo: DownloadInfo, ex: String) {}
override fun onDownloadSuccess(downloadInfo: DownloadInfo) {}
override fun onDownloadIsMultiConnection(
downloadInfo: DownloadInfo,
multiConnection: Boolean
) {}
override fun onPause(downloadInfo: DownloadInfo, paused: Boolean, reason: String) {}
override fun onRestart(
downloadInfo: DownloadInfo,
restarted: Boolean,
reason: String
) {}
override fun onResume(downloadInfo: DownloadInfo, resume: Boolean, reason: String) {}
override fun onStop(downloadInfo: DownloadInfo, stopped: Boolean, reason: String) {}
}
}
This method GDownload.singleDownload
supports the following attributes.
Property | Definition |
---|---|
url* | resource address |
name* | file name or absolute filePath |
downloadCallbacksOnMainThread | Flag indicating which thread to use for download progress callbacks |
connectionFactory | connection Factory used to make connections to the remote server |
downloadFilesRoot | optional root path that will be used for saving files. if name argument is absolute path it always take precedence if both are present. |
networkType | network type to use for downloading. |
connectionRetryCount | number of times connection factory will try to establish a connection in case of failures. |
maxNumberOfConnections | number of concurrent connections(threads). max 32 |
progressUpdateTimeMilliSec | interval difference between consective calls of onDownloadProgress method in DownloadProgressListener |
getDownloader() | retrieve the download manager associated with the download |
downloadProgressListener | Download progress Listener. |
Make sure you called GDownload.init(Lifecycle)
and then call the following method.
GDownload.freeDownloader(Context) { downloader ->
downloader.download(
"url",
"name",
NetworkType.ALL,
object : DownloadProgressListener {
override fun onDownloadSuccess(downloadInfo: DownloadInfo) {}
override fun onDownloadFailed(downloadInfo: DownloadInfo, ex: String) {}
})
}
For this way of downloading you don't need to initalize
anything.
Simply call the following
val downloader = GDownload.freeDownloader(
Context,
ScheduledBackgroundExecutor,
DownloadCallbacksHandler,
FileStorageHelper,
ConnectionManager,
DownloadDatabaseManager,
NetworkInfoProvider
)
downloader.download("url", "name", NetworkType.ALL, object : DownloadProgressListener {})
To pause a download is pretty simple.
downloader.freezeDownload {frozen,msg ->
if(frozen) {
//download froze/paused
}else {
// $msg indicates the reason
}
}
Remember when this method is called DownloadProgressListener
onPause()
is also invoked.
To stop a download is pretty simple.
downloader.stopDownload {stopped,msg ->
if(stopped) {
//download stopped
}else {
// $msg indicates the reason
}
}
Remember when this method is called DownloadProgressListener
onStop()
is also invoked.
Pause and Stop does the same job - just stop the download
.
The difference lies in a fact that Pause
Park Threads
after download is stopped and later when resume
is called, these threads
will be used.
While stop
don't park threads.
Download that is paused
can be resumed like this.
downloader.resumeDownload {resumed, msg->
if(resumed) {
// download successfully resumed
}else {
// check the $msg
}
}
Download that is 'Stopped' or Failed
can be restarted
via
downloader.restart {restarted, msg->
if(restarted) {
// download successfully restarted
}else {
// check the $msg
}
}
Make sure you called GDownload.init()
and then call use the following method
GDownload.loadAllInCompleteDownloadsFromDatabase(Context) {downloadList->
// process downloads
downloadList.forEach { download ->
GDownload.freeDownloader(Context) {
it.download(download, object : DownloadProgressListener {})
}
}
}
val dbManager = SQLiteManager.getInstance(Context)
dbManager.findDownloadByDownloadId(downloadId_int)
val allInCompleteDownloads = dbManager.findAllInCompleteDownloads()
dbManager.findDownloadByFilePath("filePath")
dbManager.close()
If you had reference to the downloader then you can directly load the failed download from database.
downloader.loadDownloadFromDatabase(DownloadID) {loaded->}
downloader.loadDownloadFromDatabase("filePath") {loaded->}
if you had reference to the GroupProcessor(Group
) then you can directly load the failed download/s from database into the group processor.
group.loadDownloadsFromDatabase {downloadsEnqueued -> }
If you want to shutdown downloader and it's best practice.
just call
downloader.shutdown()
In Order to Download Multiple files and monitor them as a single entity,
GroupDownloader can be used.
There are various ways to create one.
Make sure you called GDownload.init(Lifecycle)
and then
val downloadList = mutableListOf<Download>()
GDownload.freeGroup(Context) {
groupLoopTimeMilliSecs = 5_00
getGroup()?.apply {
start() // start group thread. as group has it's dedicated thread
addAll(downloadList) { // enqueue downloads in the group but don't issue the start command
startDownloads(it) // start group downloads
}
addGroupProgressListener(GroupListener)
}
}
see sample app for more details. The following attributes are supported for group downloader.
Property | Definition |
---|---|
groupLoopTimeMilliSecs | As each group has dedicated thread for processing Downloads.it is the time difference in milliseconds b/w two consecutive loops. |
concurrentDownloadsRunningCapacity | number of concurrent downloads that group will run. Although! there is no limit on enqueuing downloads. |
progressCallbacksOnMainThread | Flag indicating which thread to use for download progress callbacks |
urlConnectionFactory | connection Factory used to make connections to the remote server |
filesSaveRootPath | optional root path that will be used for saving files. if name argument is absolute path it always take precedence if both are present. |
networkType | network type to use for downloading. |
connectionRetryCount | number of times connection factory will try to establish a connection in case of failures. |
maxConnectionPerDownload | number of concurrent connections(threads). max 32 |
progressUpdateTimeMilliSecs | interval difference between consective calls of onDownloadProgress method in DownloadProgressListener |
getGroup() | retrieve the group download manager associated with the downloads list. |
progressCallbackLifeCycle | Lifecycle to associate the progress callbacks for individual downloads. |
databaseManager | Database manager used for loading or accessing Downloads data |
networkInfoProvider | network information provider |
group.shutDown()
To pause or freeze download running inside group processor.
group.freezeDownload(DownloadID)
To resume download running inside group processor.
group.resumeDownload(DownloadID)
To stop download running inside group processor.
group.stopDownload(DownloadID)
To restart download running inside group processor.
group.restartDownload(DownloadID)
In order to track group processor progress. attach the listener as below.
group.addGroupProgressListener(object : GroupListener {
override fun onAdded(groupId: Long, download: DownloadInfo, groupState: GroupState) {}
override fun onDownloading(
groupId: Long,
download: DownloadInfo,
groupState: GroupState
) {}
override fun onEnqueued(groupId: Long, download: DownloadInfo, groupState: GroupState) {}
override fun onFailure(
groupId: Long,
errorMessage: String?,
download: DownloadInfo,
groupState: GroupState
) {}
override fun onPaused(groupId: Long, download: DownloadInfo, groupState: GroupState) {}
override fun onStarting(groupId: Long, download: DownloadInfo, groupState: GroupState) {}
override fun onStopped(groupId: Long, download: DownloadInfo, groupState: GroupState) {}
override fun onSuccess(groupId: Long, download: DownloadInfo, groupState: GroupState) {}
override fun onWaitingForTurn(
groupId: Long,
download: DownloadInfo,
groupState: GroupState
) {}
})
By default GDownload uses the HttpUrlConnection for connecting and downloading.
In order to change or use custom Connection Handler.
you need to check out URLConnectionHandler
and DefaultURLConnectionHandler
.
Library is lifecycle aware. it has its benefits but also
some pitfalls.
Imagine if you init the library by passing the lifecycle which is associated with fragment and app stays for too long
in the background and for some reasons your activity,fragment or
lifecycle is destroyed but app is not.
in such case you might not be able to schedule further downloads
as the background executors are killed.
Either reinitialize the library or be careful while passing
the lifecycle.
Second scenario will be if you attach lifecycle to single or
group download manager and that lifecycle is destroyed or that component is destroyed. it is likely that you will not recieve progress callbacks.
so be careful
there is an activity named as SingleDownloadLifecycleSurvivalActivity
which shows how to handle lifecycle events while keeping the downloads running.
passing lifecycle is not necessary
GDownload can only get better if you make code contributions. Found a bug? Report it. Have a feature idea you'd love to see in GDownload? Contribute to the project!
Copyright (C) 2022 Tanveer Hussain.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.