项目中要使用到版本更新的功能,参考了开源中国源码,也遇到一些问题,再次记录一下:
遇到的问题:
1. 1.notifacation.bulider怎么使用?
2. notifacation和notifacation.bulider有什么区别?
3. 文件创建遇到的坑。
4. bind和service传值。
5. 退出时要注意的事项。
step1:
开始需要比对服务器的apk版本。
json结构是这样的
{"versionCode":"1","vsesionContent":"更新apk"}
查看本身的apk版本:
versionCode = Application.getContext().getPackageManager().getPackageInfo(packageName, 0).versionCode;
step2:
如果服务器版本号大于当前版本号开始下载。
在这里先说明service有两种启动方式:
第一种:startService(Intent);
第二种:bindService(Intent,ServiceConnection,flags);
两种方式关闭不一样,需要单独关闭才可以。
第一种:
Intent intent=new Intent(content,DownloadService.class);
content.startService();
第二种如果需要和activity交互数据的话可以使用。
以下是我写的启动方式
public static void openDownLoadService(Context context, String downurl,
String tilte) {
final ICallbackResult callback = new ICallbackResult() {
@Override
public void OnBackResult(Object s) {
}
};
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
DownloadService.DownloadBinder binder = (DownloadService.DownloadBinder) service;
binder.addCallback(callback);
binder.start();
}
};
Intent intent = new Intent(context, DownloadService.class);
intent.putExtra(DownloadService.BUNDLE_DOWNLOAD_URL, downurl);
intent.putExtra(DownloadService.BUNDLE_KEY_TITLE, tilte);
context.startService(intent);
context.bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
我自己写了一个接口,从onServiceConnected获取service返回的数据。
service 执行的顺序是:
oncreate->onStartCommand->Binder->onServiceConnected
在OnCreate进行一些初始化的数据。
比如:实例化 DownloadBinder,以及NotificationManager
在onServiceConnected判断是否已经可以对activity进行通讯了
如果已经连接上了就开启线程来下载apk文件
DownloadService.DownloadBinder binder = (DownloadService.DownloadBinder) service;
binder.addCallback(callback);
binder.start();
Log.d("aa", "onServiceConnected");
接下来放出 DownloadBinderBinder类
public class DownloadBinder extends Binder {
public void start() {
Log.d("aa","Binder");
if (downLoadThread == null || !downLoadThread.isAlive()) {
progress = 0;
**1**.setUpNotification();
new Thread() {
public void run() {
// 下载
**2** startDownload();
}
}.start();
}
}
public void cancel() {
canceled = true;
}
public int getProgress() {
return progress;
}
public boolean isCanceled() {
return canceled;
}
public boolean serviceIsDestroy() {
return serviceIsDestroy;
}
public void cancelNotification() {
mHandler.sendEmptyMessage(2);
}
public void addCallback(ICallbackResult callback) {
DownloadService.this.callback = callback;
}
}
先看到 1:
因为我们要在通知栏自定义下载的进度,所有我们要自己写入布局.
那么我们会使用到该类 NotificationCompat.Builder
为什么不直接使用 Notification 是因为有些方法已经hide了,比如 这个setLatestEventInfo();
setLatestEventInfo()方法来为通知初始化布局和数据。
接下来是我写的:
/** * 设置notification */
private void setUpNotification() {
CharSequence tickerText = "准备下载";
long when = System.currentTimeMillis();
NotificationBuilder = new NotificationCompat.Builder(mContext);
/** * 这个属性一定要加.不然显示不了 */
NotificationBuilder.setSmallIcon(R.mipmap.logoicon);
NotificationBuilder.setWhen(when);
NotificationBuilder.setTicker(tickerText);
NotificationBuilder.setOngoing(true);
/** * 自定义试图 */
contentView = new RemoteViews(getPackageName(), R.layout.notification_download_show);
contentView.setTextViewText(R.id.tv_download_state, mTitle);
NotificationBuilder.setContent(contentView);
/** * 设置隐身跳转---- */
// Intent intent = new Intent(this, MainActivity.class);
// PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
// intent, PendingIntent.FLAG_UPDATE_CURRENT);
// NotificationBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFY_ID, NotificationBuilder.build());
}
详细的资料可以参考
http://blog.leanote.com/post/554ca3792168cb3a61000001
然后看到 2
前提:因为service是运行在主线程中所以我们在service开启线程进行下载apk操作,然后在通过安装apk 进行更新
这里就需要设计到一个文件创建的问题了, 因为我们再次安装下载好的apk时需要一个制定下载路径。
看到这个方法
public static void installAPK(Context context, File file) {
if (file == null || !file.exists())
return;
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
context.startActivity(intent);
}
这里需要File 这个file就是我们下载apk的地方。也是因为文件这个问题,花费了我几个小时。
原来的代码:
File file = new File(AppConfig.DEFAULT_SAVE_IMAGE_PATH);
if (!file.exists()) { file.mkdir(); }
String apkFile = saveFileName;
File saveFile = new File(apkFile);
现在代码
File file = new File(AppConfig.DEFAULT_SAVE_IMAGE_PATH);
if (!file.exists()) { file.mkdirs(); }
String apkFile = saveFileName;
File saveFile = new File(apkFile);
有没有看出来有什么不同。
file.mkdirs(); file.mkdir(); 这个地方少些一个s。我心都凉了
第一个函数 如果是 xxx/load/xxx.apk 是可以创建的。支持多文件创建
第二个函数,只支持一个文件一个文件创建。
如果文件创建对了,就进行网络下载apk的操作了
代码如下:
/** * 下载文件 * * @param downloadUrl * @param saveFile * @return * @throws IOException */
private long downloadUpdateFile(String downloadUrl, File saveFile) throws IOException {
int downloadCount = 0;
int currentSize = 0;
long totalSize = 0;
int updateTotalSize = 0;
HttpURLConnection httpConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL(downloadUrl);
httpConnection = (HttpURLConnection) url.openConnection();
httpConnection
.setRequestProperty("User-Agent", "PacificHttpClient");
if (currentSize > 0) {
httpConnection.setRequestProperty("RANGE", "bytes="
+ currentSize + "-");
}
httpConnection.setConnectTimeout(10000);
httpConnection.setReadTimeout(20000);
httpConnection.setRequestMethod("GET");
updateTotalSize = httpConnection.getContentLength();
if (httpConnection.getResponseCode() == 404) {
throw new Exception("fail!");
}
is = httpConnection.getInputStream();
fos = new FileOutputStream(saveFile, false);
byte buffer[] = new byte[1024];
int readsize = 0;
while ((readsize = is.read(buffer)) > 0) {
fos.write(buffer, 0, readsize);
totalSize += readsize;
// 为了防止频繁的通知导致应用吃紧,百分比增加10才通知一次
if ((downloadCount == 0)
|| (int) (totalSize * 100 / updateTotalSize) - 10 >= downloadCount) {
downloadCount += 10;
// 更新进度
Message msg = mHandler.obtainMessage();
msg.what = 1;
msg.arg1 = downloadCount;
mHandler.sendMessage(msg);
if (callback != null)
callback.OnBackResult(progress);
}
}
// 下载完成通知安装
mHandler.sendEmptyMessage(0);
// 下载完了,cancelled也要设置
canceled = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (httpConnection != null) {
httpConnection.disconnect();
}
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
}
return totalSize;
}
我这里使用到的是原始的网络请求 配合handler进行通知栏的更新
private Handler mHandler = new Handler() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what) {
case 0:
// 下载完毕
mNotificationManager.cancel(NOTIFY_ID);
installApk();
break;
case 2:
// 取消通知
mNotificationManager.cancel(NOTIFY_ID);
break;
case 1:
int rate = msg.arg1;
if (rate < 100) {
contentView.setTextViewText(R.id.tv_download_state, mTitle + "(" + rate
+ "%" + ")");
contentView.setProgressBar(R.id.pb_download, 100, rate,
false);
} else {
// 下载完毕后变换通知形式
NotificationBuilder.setAutoCancel(true);
serviceIsDestroy = true;
stopSelf();// 停掉服务自身
}
mNotificationManager.notify(NOTIFY_ID, NotificationBuilder.build());
break;
}
}
};
对了 最后还需要注意一个问题:
就是程序如果是 System.exit(0); 这样退出的话,那么通知栏就会在下载了 service也会终止掉
效果就是这样
参考
Android Service完全解析,关于服务你所需知道的一切(下)
Android 状态栏通知Notification详解
开源中国app源码