创建一个Service,用于下载apk文件,service下载的时候实时更新通知栏的下载进度,同时通过回调,把下载情况的回调传给调用者,实现远程服务下载功能
必须在Manifest中注册Service:
<!--更新服务-->
<service android:name="com.uadzd.update.service.DownloadService"
android:exported="true"
android:process=":update"
/>
代码实现
public class DownloadService extends Service {
private static final int NOTIFY_ID = 0;
private static final String CHANNEL_ID = "app_update_id";
private static final CharSequence CHANNEL_NAME = "app_update_channel";
private static ServiceConnection connection;
private static Context context;
private NotificationManager mNotificationManager;
private DownloadBinder binder = new DownloadBinder();
private NotificationCompat.Builder mBuilder;
private static IDownloadCallback mCallBack = null;
public static void bindService(Context context, ServiceConnection connection) {
DownloadService.context = context;
DownloadService.connection = connection;
Intent intent = new Intent(context, DownloadService.class);
context.startService(intent);
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
public static void unBindService(){
if (context != null && connection != null){
context.unbindService(connection);
mCallBack = null;
connection = null;
}
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
public IBinder onBind(Intent intent) {
// 返回自定义的DownloadBinder实例
return binder;
}
@Override
public void onDestroy() {
mNotificationManager = null;
super.onDestroy();
}
/**
* 创建通知
*/
private void setUpNotification() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.enableVibration(false);
channel.enableLights(false);
mNotificationManager.createNotificationChannel(channel);
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setContentTitle("开始下载")
.setContentText("正在连接服务器")
.setSmallIcon(R.drawable.lib_update_app_update_icon)
.setLargeIcon(AppUpdateUtils.drawableToBitmap(AppUpdateUtils.getAppIcon(DownloadService.this)))
.setOngoing(true)
.setAutoCancel(true)
.setWhen(System.currentTimeMillis());
mNotificationManager.notify(NOTIFY_ID, mBuilder.build());
}
/**
* 下载模块
*/
private void startDownload(UpdateAppBean updateApp,IDownloadCallback callback) {
UpdateTask updateTask = new UpdateTask(new FileDownloadCallBack(callback));
updateTask.execute(updateApp);
}
private void stop(String contentText) {
if (mBuilder != null) {
mBuilder.setContentTitle(AppUpdateUtils.getAppName(DownloadService.this))
.setContentText(contentText);
Notification notification = mBuilder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(NOTIFY_ID, notification);
}
close();
}
private void close() {
stopSelf();
}
/**
* DownloadBinder中定义了一些实用的方法
*
* @author user
*/
public class DownloadBinder extends IDownloadlInterface.Stub{
@Override
public void start(UpdateAppBean bean, IBinder iBinder) throws RemoteException {
/**
* 开始下载
*
*/
IDownloadCallback callback = IDownloadCallback.Stub.asInterface(iBinder);
startDownload(bean,callback);
}
@Override
public void stop(String msg) throws RemoteException {
}
}
public class FileDownloadCallBack implements FileCallBack {
int oldRate = 0;
public FileDownloadCallBack(@Nullable IDownloadCallback callback) {
super();
mCallBack = callback;
}
@Override
public void onBefore() {
//初始化通知栏
setUpNotification();
if (mCallBack != null) {
try {
mCallBack.onStart();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onProgress(float progress, long total) {
//做一下判断,防止自回调过于频繁,造成更新通知栏进度过于频繁,而出现卡顿的问题。
int rate = Math.round(progress * 100);
if (oldRate != rate) {
if (mCallBack != null) {
try {
mCallBack.setMax(total);
mCallBack.onProgress(progress, total);
} catch (RemoteException e) {
e.printStackTrace();
}
}
if (mBuilder != null) {
// 创建一个数值格式化对象
NumberFormat numberFormat = NumberFormat.getInstance();
// 设置精确到小数点后2位
numberFormat.setMaximumFractionDigits(2);
String result = numberFormat.format((float) progress / (float) total * 100);
mBuilder.setContentTitle("正在下载:" + AppUpdateUtils.getAppName(DownloadService.this))
.setContentText(result+"%")
.setProgress((int) total, (int) progress, false)
.setWhen(System.currentTimeMillis());
Notification notification = mBuilder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONLY_ALERT_ONCE;
mNotificationManager.notify(NOTIFY_ID, notification);
}
//重新赋值
oldRate = rate;
}
}
@Override
public void onError(String error) {
Toast.makeText(DownloadService.this, "更新新版本出错," + error, Toast.LENGTH_SHORT).show();
//App前台运行
if (mCallBack != null) {
try {
mCallBack.onError(error);
} catch (RemoteException e) {
e.printStackTrace();
}
}
try {
mNotificationManager.cancel(NOTIFY_ID);
close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
@Override
public void onResponse(String filePath) {
try {
if (AppUpdateUtils.isAppOnForeground(DownloadService.this) || mBuilder == null) {
//App前台运行
mNotificationManager.cancel(NOTIFY_ID);
if (mCallBack != null) {
boolean temp = mCallBack.onInstallAppAndAppOnForeground(filePath);
mCallBack.onFinish(filePath);
if (!temp) {
AppUpdateUtils.installApp(DownloadService.this, filePath);
}
} else {
AppUpdateUtils.installApp(DownloadService.this, filePath);
}
} else {
//App后台运行, 点击通知栏安装
//更新参数,注意flags要使用FLAG_UPDATE_CURRENT
Intent installAppIntent = AppUpdateUtils.getInstallAppIntent(DownloadService.this,filePath);
PendingIntent contentIntent = PendingIntent.getActivity(DownloadService.this, 0, installAppIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(contentIntent)
.setContentTitle(AppUpdateUtils.getAppName(DownloadService.this))
.setContentText("下载完成,请点击安装")
.setProgress(0, 0, false)
// .setAutoCancel(true)
.setDefaults((Notification.DEFAULT_ALL));
Notification notification = mBuilder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(NOTIFY_ID, notification);
}
//下载完关闭
close();
} catch (Exception e) {
e.printStackTrace();
} finally {
close();
}
}
}
}
创建AIDL文件,android中跨进程通讯只能使用AIDL
AIDL文件中接口函数的参数只支持以下几种方式
1. Java 的原生类型
2. String 和CharSequence
3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型; 以上三种类型都不需要导入(import)
4. AIDL 自动生成的接口 需要导入(import)
5. 实现android.os.Parcelable 接口的类. 需要导入(import)。
service中的binder类
interface IDownloadlInterface {
void start(in UpdateAppBean bean,IBinder callback);
void stop(String msg);
}
##接口中start(in UpdateAppBean bean,IBinder callback);方法的第一个参数,是一个类UpdateAppBean.class必须实现Parcelable接口。而且必须在与UpdateAppBean.class同一个包下增加UpdateAppBean.aidl
###UpdateAppBean.aidl文件内容:
package com.uadzd.update;//包名
parcelable UpdateAppBean;//类名
public class UpdateAppBean implements Parcelable {
private String newVersionName;
private String newVersionCode;
private String oldVersionCode;
private String downloadUrl;
private String updateMsg;
private String targetPath;
public UpdateAppBean(){
}
protected UpdateAppBean(Parcel in) {
newVersionName = in.readString();
newVersionCode = in.readString();
oldVersionCode = in.readString();
downloadUrl = in.readString();
updateMsg = in.readString();
targetPath= in.readString();
}
public String getNewVersionName() {
return newVersionName;
}
public void setNewVersionName(String newVersionName) {
this.newVersionName = newVersionName;
}
public String getNewVersionCode() {
return newVersionCode;
}
public void setNewVersionCode(String newVersionCode) {
this.newVersionCode = newVersionCode;
}
public String getOldVersionCode() {
return oldVersionCode;
}
public void setOldVersionCode(String oldVersionCode) {
this.oldVersionCode = oldVersionCode;
}
public String getDownloadUrl() {
return downloadUrl;
}
public void setDownloadUrl(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
public String getUpdateMsg() {
return updateMsg;
}
public void setUpdateMsg(String updateMsg) {
this.updateMsg = updateMsg;
}
public String getTargetPath() {
return targetPath;
}
public void setTargetPath(String targetPath) {
this.targetPath = targetPath;
}
public static final Creator<UpdateAppBean> CREATOR = new Creator<UpdateAppBean>() {
@Override
public UpdateAppBean createFromParcel(Parcel in) {
return new UpdateAppBean(in);
}
@Override
public UpdateAppBean[] newArray(int size) {
return new UpdateAppBean[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(newVersionName);
dest.writeString(newVersionCode);
dest.writeString(oldVersionCode);
dest.writeString(downloadUrl);
dest.writeString(updateMsg);
dest.writeString(targetPath);
}
}
接口中start(in UpdateAppBean bean,IBinder callback);方法的第二个参数是一个接口类型,使用接口类型作为参数(用于回调)的时候,接口文件必须定义为***.aidl,不能跟平时定义接口一样直接 ***.java,IDownloadCallback 为IBinder的基类,网上看了很多写法都是直接第二个参数为IDownloadCallback ,然后导入对应的包。start(in UpdateAppBean bean,IDownloadCallback callback);
这里有坑,一直在service拿到的回调是空,应该使用IBinder 最为参数,然后再通过
IDownloadCallback callback = IDownloadCallback.Stub.asInterface(iBinder);进行转换。
interface IDownloadCallback {
void onStart();
/**
* 进度
*
* @param progress 进度 0.00 -1.00 ,总大小
* @param totalSize 总大小 单位M
*/
void onProgress(float progress, long totalSize);
/**
* 总大小
*
* @param totalSize 单位M
*/
void setMax(long totalSize);
/**
* 下载完了
*
* @param filePath 下载的app
* @return true :下载完自动跳到安装界面,false:则不进行安装
*/
boolean onFinish(String filePath);
/**
* 下载异常
*
* @param msg 异常信息
*/
void onError(String msg);
/**
* 当应用处于前台,准备执行安装程序时候的回调,
*
* @param filePath 当前安装包路径
* @return false 默认 false ,当返回时 true 时,需要自己处理 ,前提条件是 onFinish 返回 false 。
*/
boolean onInstallAppAndAppOnForeground(String filePath);
}
FileCallback接口类,文件下载进度回调接口
public interface FileCallBack {
void onProgress(float progress, long total);
/**
* 错误回调
*
* @param error 错误提示
*/
void onError(String error);
/**
* 结果回调
*
* @param filePaht 下载好的文件路径
*/
void onResponse(String filePaht);
/**
* 请求之前
*/
void onBefore();
}
下载工具类
package com.uadzd.update;
public class UpdateTask extends AsyncTask<UpdateAppBean, Integer, String> {
private DownloadService.FileDownloadCallBack fileDownloadCallBack;
public UpdateTask(DownloadService.FileDownloadCallBack fileDownloadCallBack) {
this.fileDownloadCallBack = fileDownloadCallBack;
}
@Override
protected void onPreExecute() {
if (fileDownloadCallBack != null){
fileDownloadCallBack.onBefore();
}
super.onPreExecute();
}
@Override
protected String doInBackground(UpdateAppBean... params) {
UpdateAppBean updateAppBean = params[0];
HttpURLConnection conn = null;
File file = null;
String path = AppUpdateUtils.appIsDownloaded(updateAppBean);
if (path != null){
return path;
}
//如果相等的话表示当前的sdcard挂载在手机上并且是可用的
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
final OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(updateAppBean.getDownloadUrl())
.build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
long totalLen = response.body().contentLength();
InputStream is = response.body().byteStream();
//下载到本地的app名
file = AppUpdateUtils.getAppFile(updateAppBean);
FileOutputStream fos = new FileOutputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024];
int len;
int total = 0;
while ((len = bis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
total += len;
//获取当前下载量
publishProgress(total, (int) totalLen);
}
fos.close();
bis.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return file.getAbsolutePath();
}else {
return null;
}
}
@Override
protected void onProgressUpdate(Integer... values) {
if (fileDownloadCallBack != null){
fileDownloadCallBack.onProgress(values[0] / 1024 / 1024, values[1] / 1024 / 1024);
}
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(String filePaht) {
if (filePaht != null) {
if (fileDownloadCallBack != null){
fileDownloadCallBack.onResponse(filePaht);
}
} else {
if (fileDownloadCallBack != null){
fileDownloadCallBack.onError("没有可用于下载的存储空间!");
}
}
}
}
调用远程服务的方法,与上面service不在同一个进程
private void startUpdate(final boolean isInBackground) {
String updateUrl = "http://imtt.dd.qq.com/16891/E29FAA923B6DA21BEA23C2EC4C6F7BC9.apk?fsname=com.uadzd_2.0_6.apk&csr=1bbd";
final UpdateAppBean updateAppBean = new UpdateAppBean();
updateAppBean.setDownloadUrl(updateUrl);
updateAppBean.setNewVersionName("1.1.4");
DownloadService.bindService(this.getApplicationContext(), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IDownloadlInterface iDownloadlInterface = IDownloadlInterface.Stub.asInterface(service);
try {
iDownloadlInterface.start(updateAppBean, new IDownloadCallback.Stub() {
@Override
public void onStart() throws RemoteException {
if (isInBackground){
goNext();
}else {
if (pd == null){
showProgressDialog();
}
}
}
@Override
public void onProgress(float progress, long totalSize) throws RemoteException {
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setMaximumFractionDigits(2);
String result = numberFormat.format((float) progress / (float) totalSize * 100);
Log.i("aaa", "result"+result);
if (!isInBackground){
if (pd == null){
showProgressDialog();
}else {
pd.setMax((int) totalSize);
pd.setProgress((int) progress);
}
}
}
@Override
public void setMax(long totalSize) throws RemoteException {
}
@Override
public boolean onFinish(String filePath) throws RemoteException {
Log.i("aaa", "onFinish:"+filePath);
if (pd != null ){
pd.dismiss();
}
return false;
}
@Override
public void onError(String msg) throws RemoteException {
}
@Override
public boolean onInstallAppAndAppOnForeground(String filePath) throws RemoteException {
//false 后台下载完提示安装,true下载完不进行操作,自行处理
return false;
}
@Override
public IBinder asBinder() {
return null;
}
});
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
});
}