0
点赞
收藏
分享

微信扫一扫

Android基础实战之下载实践 | OkHttp | 带实例

五殳师兄 2022-02-18 阅读 68

下载实践

  • DownloadListener
/**
* Description
* <p>
* 定义一个回调接口,用于对下载过程中的各种状态进行监听和回调
* @author qricis on 2020/8/28 15:14
* @version 1.0.0
*/

public interface DownloadListener {

void onProgress(int progress);

void onSuccess();

void onFailed();

void onPaused();

void onCanceled();

}
  • DownloadTask
/**
* Description
* <p>
* 下载功能类服务类
* 第一个泛型表示要传入一个字符串给后台任务
* 第二个泛型表示使用整型数据来作为进度的显示单位
* 第三个泛型表示使用整型数据来反馈执行结果
* @author qricis on 2020/8/28 15:28
* @version 1.0.0
*/

public class DownloadTask extends AsyncTask<String,Integer,Integer> {

/**
* 定义四个整型表示下载状态成功、失败、暂停、取消
* */

public static final int TYPE_SUCCESS = 0;
public static final int TYPE_FAILED = 1;
public static final int TYPE_PAUSED = 2;
public static final int TYPE_CANCELED = 3;

/**
* 在构造中通过传入的该参数进行回调
* */

private DownloadListener mDownloadListener;

private boolean isCanceled = false;
private boolean isPaused = false;
private int lastProgress;

public DownloadTask(DownloadListener listener) {
mDownloadListener = listener;
}

/**
* 用于在后台执行具体的下载逻辑
* 在子线程运作
* 参数为下载地址
* 返回当前执行进度可以调用publishProgress
* */

@Override
protected Integer doInBackground(String... strings) {

InputStream inputStream = null;
RandomAccessFile randomAccessFile = null;
File file = null;

try {
// 记录一下载的长度
long downloadedLength = 0;

// 获取下载的URL地址
String downloadUrl = strings[0];
// 通过下载地址解析出下载的文件名
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
// 将文件下载到Environment.DIRECTORY_DOWNLOADS目录下
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
// 读取文件地址
file = new File(directory + fileName);

// 判断是否已经存在要下载的文件,是的话就读取已下载的字节数,这样可以在后面用断点续传的功能
if (file.exists()) {
downloadedLength = file.length();
}

// 获取下载文件的总长度,并赋值给contentLength
long contentLength = getContentLength(downloadUrl);

// 若文件长度为0,则文件有问题,直接返回false
if (contentLength == 0) {
return TYPE_FAILED;
// 若文件长度已经等于已下载文件的长度,则返回success
} else if (contentLength == downloadedLength) {
// 已下载字节与文件总字节数相等,表示下载完成
return TYPE_SUCCESS;
}

// 使用okhttp发送一条网络请求
OkHttpClient okHttpClient = new OkHttpClient();

Request request = new Request.Builder()
// 断点下载,指定从哪个字节开始下载
.addHeader("RANGE","bytes=" + downloadedLength + "-")
.url(downloadUrl)
.build();

// 判断请求是否通过
Response response = okHttpClient.newCall(request).execute();

if (response != null) {

inputStream = response.body().byteStream();
randomAccessFile = new RandomAccessFile(file,"rw");
// 跳过已下载的字节
randomAccessFile.seek(downloadedLength);

byte[] bytes = new byte[2014];
int total = 0;
int len;

// 判断在下载途中,用户有没有触发停止或者取消操作
while ((len = inputStream.read(bytes)) != -1) {
// 有的话,则做出相应的回应
if (isCanceled) {
return TYPE_CANCELED;
} else if (isPaused) {
return TYPE_PAUSED;
} else {
// 没有就实时计算当前的下载进度
total += len;
randomAccessFile.write(bytes,0,len);
// 计算已下载的百分比
int progress = (int)((total + downloadedLength) * 100 / contentLength);
// 调用该方法进行通知
// 调用该方法,onProgressUpdate会很快被调用
publishProgress(progress);
}
}

response.body().close();
return TYPE_SUCCESS;
}

} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
if (isCanceled && file != null) {
file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}

return TYPE_FAILED;
}

/**
* 后台调用publishProgress时,该方法被调用
* 用于在界面上更新当前的下载进度
* 从参数中获取到当前的下载进度,然后与上一次的进度进行对比
* 有变化的话,就调用mDownloadListener.onProgress(progress)来通知下载进度更新
* */

@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
mDownloadListener.onProgress(progress);
lastProgress = progress;
}
}

/**
* 当后台进程执行完毕return时,该方法被调用
* 用于通知最终的下载结果
* 根据参数中传入的下载状态来进行回调
* */

@Override
protected void onPostExecute(Integer integer) {
switch (integer) {
case TYPE_SUCCESS:
mDownloadListener.onSuccess();
break;
case TYPE_FAILED:
mDownloadListener.onFailed();
break;
case TYPE_PAUSED:
mDownloadListener.onPaused();
break;
case TYPE_CANCELED:
mDownloadListener.onCanceled();
break;
default:
break;
}
}

public void pauseDownload() {
isPaused = true;
}

public void canceledDownload() {
isCanceled = true;
}

private long getContentLength(String downloadUrl) throws IOException {

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
.url(downloadUrl)
.build();

Response response = client.newCall(request).execute();

if (response != null && response.isSuccessful()) {
long contentLength = response.body().contentLength();
response.body().close();
return contentLength;
}

return 0;
}
}
  • DownloadService
/**
* Description
* <p>
* 为保证DownloadTask一直在后台运行,创建一个下载的服务类
* @author qricis on 2020/8/31 14:12
* @version 1.0.0
*/

public class DownloadService extends Service {

private DownloadTask mDownloadTask;
private String downloadUrl;

/**
* 创建了DownloaderListener的匿名类实例,并实现了五个方法
* 调用getNotification方法构建了一个用于显示下载的通知
* 方法中调用了NotificationManager的notify()方法去触发这个通知
* */

private DownloadListener mDownloadListener = new DownloadListener() {
@Override
public void onProgress(int progress) {
getNotificationManager().notify(1,getNotification("Downloading...",progress));
}

@Override
public void onSuccess() {

mDownloadTask = null;
// 下载成功时。将前台服务通知关闭,并创建一个下载成功的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Success",-1));
Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show();

}

@Override
public void onFailed() {

mDownloadTask = null;
// 下载失败时将前台服务通知关闭,并创建一个下载失败的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Failed",-1));
Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();


}

@Override
public void onPaused() {

mDownloadTask = null;
Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();

}

@Override
public void onCanceled() {

mDownloadTask = null;
// 下载取消时将前台服务通知关闭
stopForeground(true);
Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();

}
};

private DownloadBinder mDownloadBinder = new DownloadBinder();

@Nullable
@Override
public IBinder onBind(Intent intent) {
return mDownloadBinder;
}

/**
* 用于使DownloadService可以和活动进行通信
* */

class DownloadBinder extends Binder {

/**
* 创建了一个DownloadTask实例,把上面的DownloadListener实例作为参数传入
* 调用execute开启下载,并将下载文件的URL地址传入到execute()方法中
* 调用startForeground()方法,使之成为一个前台服务
* */

@RequiresApi(api = Build.VERSION_CODES.Q)
public void startDownload(String url) {
if (mDownloadTask == null) {
downloadUrl = url;
mDownloadTask = new DownloadTask(mDownloadListener);
mDownloadTask.execute(downloadUrl);

startForeground(1,getNotification("Downloading...",0));
Toast.makeText(DownloadService.this,"Downlading...",Toast.LENGTH_SHORT).show();

}
}

public void pauseDownload() {
if (mDownloadTask != null) {
mDownloadTask.pauseDownload();
}
}

public void cancelDownload() {
if (mDownloadTask != null) {
mDownloadTask.canceledDownload();
} else {
if (downloadUrl != null) {
// 取消下载时需将文件删除,并将通知关闭
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
File file = new File(directory + fileName);
if (file.exists()) {
file.delete();
}
getNotificationManager().cancel(1);
stopForeground(true);
Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();
}
}
}
}

/**
* 返回一个NotificationManager
* */

private NotificationManager getNotificationManager() {
return (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
}

/**
* 构建一个用于显示下载进度的通知
* */

private Notification getNotification(String title, int progress) {

// 这里的id里面输入自己的项目的包的路径
String ID = "com.example.servicebestpractice";
String NAME = "Channel One";

Intent intent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);

// 创建服务对象
NotificationCompat.Builder notification = new NotificationCompat.Builder(this);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 设置channel
NotificationChannel channel = new NotificationChannel(ID, NAME, manager.IMPORTANCE_HIGH);
channel.enableLights(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
manager.createNotificationChannel(channel);
// 设置channelId
notification.setChannelId(ID);
}
// 设置通知的其他显示信息
notification.setContentTitle(title)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pendingIntent);

if (progress >= 0) {
// 当progress大于或等于0时才显示下载进度
notification.setContentText(progress + "%");
// 第一个参数是传入通知的最大进度,第二个参数是传入通知的当前进度,第三个参数表示是否使用模糊进度条
notification.setProgress(100,progress,false);
}

return notification.build();
}
}
  • activity_main
<Button
android:id="@+id/start_download"
android:layout_width="198dp"
android:layout_height="62dp"
android:layout_marginTop="68dp"
android:text="@string/start_download"
android:onClick="startDownload"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />


<Button
android:id="@+id/pause_download"
android:layout_width="198dp"
android:layout_height="62dp"
android:layout_marginTop="16dp"
android:text="@string/pause_download"
android:onClick="pauseDownload"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start_download" />


<Button
android:id="@+id/stop_download"
android:layout_width="198dp"
android:layout_height="62dp"
android:layout_marginTop="16dp"
android:text="@string/stop_download"
android:onClick="cancelDownload"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pause_download" />
  • MainActivity
public class MainActivity extends AppCompatActivity {

private DownloadService.DownloadBinder mDownloadBinder;

// 创建一个ServiceConnection的匿名类
private ServiceConnection mConnection = new ServiceConnection() {
// 获取DownloadBinder实例,用于在活动中调用服务提供的各种方法
// 将活动与服务绑定
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mDownloadBinder = (DownloadService.DownloadBinder)service;
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 调用startService和bindService来启动和绑定服务
// 启动服务让DownloadService一直在后台运行
// 绑定服务让MainActivity和DownloadService进行通信
Intent intent = new Intent(this,DownloadService.class);
startService(intent);
bindService(intent,mConnection,BIND_AUTO_CREATE);

// 进行权限申请
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
}

@RequiresApi(api = Build.VERSION_CODES.Q)
public void startDownload(View view) {

if (mDownloadBinder == null){
return;
}

String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
mDownloadBinder.startDownload(url);

}

public void pauseDownload(View view) {
mDownloadBinder.pauseDownload();
}

public void cancelDownload(View view) {
mDownloadBinder.cancelDownload();
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}

@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
  • Manifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<service
android:name=".DownloadService"
android:enabled="true"
android:exported="true">
</service>

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
  • 下载地址
举报

相关推荐

0 条评论