这几天在实现一个APK版本更新的功能,发现涉及的东西比较繁杂。本着一劳永逸的想法将相关的内容写成了相对比较独立的类供以后参考同时也与大家共享,欢迎大家批评指正
主要实现了一下几个类:
(1)文件下载:设计自定义类,只需传入一个Handler、下载地址URLStr及保存路径及可实现下载的功能。handler主要用于线程间通信,跟新通知中的进度条。
对于handler发送消息更新UI线程实现进度展示的时候一定注意不要太过频繁,过设置计数器隔一定时间才发送消息,不然容易引起系统奔溃
(2) 通知(Notification):提供系统默认自带形式以及自定义通知栏布局两种形式。
(3) 服务:后台服务,startService启动模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
package
com.example.test;
import
java.io.BufferedInputStream;
import
java.io.File;
import
java.io.FileNotFoundException;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.io.InputStream;
import
java.net.HttpURLConnection;
import
java.net.MalformedURLException;
import
android.annotation.SuppressLint;
import
android.os.Environment;
import
android.os.Handler;
import
android.os.Message;
import
android.os.StrictMode;
import
android.util.Log;
@SuppressLint
(
"NewApi"
)
public
class
DownFileThread
implements
Runnable {
public
final
static
int
DOWNLOAD_COMPLETE = -
2
;
public
final
static
int
DOWNLOAD_FAIL = -
1
;
public
final
static
String TAG =
"DownFileThread"
;
Handler mHandler;
//传入的Handler,用于像Activity或service通知下载进度
String urlStr;
//下载URL
File apkFile;
//文件保存路径
boolean
isFinished;
//下载是否完成
boolean
interupted=
false
;
//是否强制停止下载线程
public
DownFileThread(Handler handler,String urlStr,String filePath)
{
Log.i(TAG, urlStr);
this
.mHandler=handler;
this
.urlStr=urlStr;
apkFile=
new
File(filePath);
isFinished=
false
;
}
public
File getApkFile()
{
if
(isFinished)
return
apkFile;
else
return
null
;
}
public
boolean
isFinished() {
return
isFinished;
}
/**
* 强行终止文件下载
*/
public
void
interuptThread()
{
interupted=
true
;
}
@Override
public
void
run() {
// TODO Auto-generated method stub
if
(Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
java.net.URL url =
null
;
HttpURLConnection conn =
null
;
InputStream iStream =
null
;
// if (DEVELOPER_MODE)
{
StrictMode.setThreadPolicy(
new
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
// or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(
new
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
try
{
url =
new
java.net.URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(
5000
);
conn.setReadTimeout(
20000
);
iStream = conn.getInputStream();
}
catch
(MalformedURLException e) {
Log.i(TAG,
"MalformedURLException"
);
e.printStackTrace();
}
catch
(Exception e) {
Log.i(TAG,
"获得输入流失败"
);
e.printStackTrace();
}
FileOutputStream fos =
null
;
try
{
fos =
new
FileOutputStream(apkFile);
}
catch
(FileNotFoundException e) {
Log.i(TAG,
"获得输出流失败:new FileOutputStream(apkFile);"
);
e.printStackTrace();
}
BufferedInputStream bis =
new
BufferedInputStream(iStream);
byte
[] buffer =
new
byte
[
1024
];
int
len;
// 获取文件总长度
int
length = conn.getContentLength();
double
rate=(
double
)
100
/length;
//最大进度转化为100
int
total =
0
;
int
times=
0
;
//设置更新频率,频繁操作UI线程会导致系统奔溃
try
{
Log.i(
"threadStatus"
,
"开始下载"
);
while
(
false
==interupted && ((len = bis.read(buffer)) != -
1
)) {
fos.write(buffer,
0
, len);
// 获取已经读取长度
total += len;
int
p=(
int
)(total*rate);
Log.i(
"num"
, rate+
","
+total+
","
+p);
if
(times>=
512
|| p==
100
)
{
/*
这是防止频繁地更新通知,而导致系统变慢甚至崩溃。
非常重要。。。。。*/
Log.i(
"time"
,
"time"
);
times=
0
;
Message msg = Message.obtain();
msg.what =p ;
mHandler.sendMessage(msg);
}
times++;
}
fos.close();
bis.close();
iStream.close();
if
(total==length)
{
isFinished=
true
;
mHandler.sendEmptyMessage(DOWNLOAD_COMPLETE);
Log.i(TAG,
"下载完成结束"
);
}
Log.i(TAG,
"强制中途结束"
);
//mhandler.sendEmptyMessage(4);
}
catch
(IOException e) {
Log.i(TAG,
"异常中途结束"
);
mHandler.sendEmptyMessage(DOWNLOAD_FAIL);
e.printStackTrace();
}
}
else
{
Log.i(TAG,
"外部存储卡不存在,下载失败!"
);
mHandler.sendEmptyMessage(DOWNLOAD_FAIL);
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
package
com.example.test;
import
android.app.Notification;
import
android.app.NotificationManager;
import
android.app.PendingIntent;
import
android.content.Context;
import
android.widget.RemoteViews;
/**
* Notification类,既可用系统默认的通知布局,也可以用自定义的布局
*
* @author lz
*
*/
public
class
MyNotification {
public
final
static
int
DOWNLOAD_COMPLETE = -
2
;
public
final
static
int
DOWNLOAD_FAIL = -
1
;
Context mContext;
//Activity或Service上下文
Notification notification;
//notification
NotificationManager nm;
String titleStr;
//通知标题
String contentStr;
//通知内容
PendingIntent contentIntent;
//点击通知后的动作
int
notificationID;
//通知的唯一标示ID
int
iconID;
//通知栏图标
long
when = System.currentTimeMillis();
RemoteViews remoteView=
null
;
//自定义的通知栏视图
/**
*
* @param context Activity或Service上下文
* @param contentIntent 点击通知后的动作
* @param id 通知的唯一标示ID
*/
public
MyNotification(Context context,PendingIntent contentIntent,
int
id) {
// TODO Auto-generated constructor stub
mContext=context;
notificationID=id;
this
.contentIntent=contentIntent;
this
.nm=(NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
}
/**
* 显示自定义通知
* @param icoId 自定义视图中的图片ID
* @param titleStr 通知栏标题
* @param layoutId 自定义布局文件ID
*/
public
void
showCustomizeNotification(
int
icoId,String titleStr,
int
layoutId) {
this
.titleStr=titleStr;
notification=
new
Notification(R.drawable.ic_launcher, titleStr, when);
notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.contentIntent=
this
.contentIntent;
// 1、创建一个自定义的消息布局 view.xml
// 2、在程序代码中使用RemoteViews的方法来定义image和text。然后把RemoteViews对象传到contentView字段
if
(remoteView==
null
)
{
remoteView =
new
RemoteViews(mContext.getPackageName(),layoutId);
remoteView.setImageViewResource(R.id.ivNotification,icoId);
remoteView.setTextViewText(R.id.tvTitle, titleStr);
remoteView.setTextViewText(R.id.tvTip,
"开始下载"
);
remoteView.setProgressBar(R.id.pbNotification,
100
,
0
,
false
);
notification.contentView = remoteView;
}
nm.notify(notificationID, notification);
}
/**
* 更改自定义布局文件中的进度条的值
* @param p 进度值(0~100)
*/
public
void
changeProgressStatus(
int
p)
{
if
(notification.contentView!=
null
)
{
if
(p==DOWNLOAD_FAIL)
notification.contentView.setTextViewText(R.id.tvTip ,
"下载失败! "
);
else
if
(p==
100
)
notification.contentView.setTextViewText(R.id.tvTip ,
"下载完成,请点击安装"
);
else
notification.contentView.setTextViewText(R.id.tvTip ,
"进度("
+p+
"%) : "
);
notification.contentView.setProgressBar(R.id.pbNotification,
100
, p,
false
);
}
nm.notify(notificationID, notification);
}
public
void
changeContentIntent(PendingIntent intent)
{
this
.contentIntent=intent;
notification.contentIntent=intent;
}
/**
* 显示系统默认格式通知
* @param iconId 通知栏图标ID
* @param titleText 通知栏标题
* @param contentStr 通知栏内容
*/
public
void
showDefaultNotification(
int
iconId,String titleText,String contentStr) {
this
.titleStr=titleText;
this
.contentStr=contentStr;
this
.iconID=iconId;
notification=
new
Notification();
notification.tickerText=titleStr;
notification.icon=iconID;
notification.flags = Notification.FLAG_INSISTENT;
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.contentIntent=
this
.contentIntent;
// 添加声音效果
// notification.defaults |= Notification.DEFAULT_SOUND;
// 添加震动,后来得知需要添加震动权限 : Virbate Permission
// mNotification.defaults |= Notification.DEFAULT_VIBRATE ;
//添加状态标志
//FLAG_AUTO_CANCEL 该通知能被状态栏的清除按钮给清除掉
//FLAG_NO_CLEAR 该通知能被状态栏的清除按钮给清除掉
//FLAG_ONGOING_EVENT 通知放置在正在运行
//FLAG_INSISTENT 通知的音乐效果一直播放
notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
changeNotificationText(contentStr);
}
/**
* 改变默认通知栏的通知内容
* @param content
*/
public
void
changeNotificationText(String content)
{
notification.setLatestEventInfo(mContext, titleStr, content,contentIntent);
// 设置setLatestEventInfo方法,如果不设置会App报错异常
// NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//注册此通知
// 如果该NOTIFICATION_ID的通知已存在,会显示最新通知的相关信息 ,比如tickerText 等
nm.notify(notificationID, notification);
}
/**
* 移除通知
*/
public
void
removeNotification()
{
// 取消的只是当前Context的Notification
nm.cancel(notificationID);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
package
com.example.test;
import
java.io.File;
import
android.app.Notification;
import
android.app.NotificationManager;
import
android.app.PendingIntent;
import
android.app.Service;
import
android.content.Context;
import
android.content.Intent;
import
android.net.Uri;
import
android.os.Environment;
import
android.os.Handler;
import
android.os.IBinder;
import
android.os.Message;
import
android.provider.Settings.Global;
import
android.util.Log;
public
class
DownloadServices
extends
Service {
private
final
static
int
DOWNLOAD_COMPLETE = -
2
;
private
final
static
int
DOWNLOAD_FAIL = -
1
;
//自定义通知栏类
MyNotification myNotification;
String filePathString;
//下载文件绝对路径(包括文件名)
//通知栏跳转Intent
private
Intent updateIntent =
null
;
private
PendingIntent updatePendingIntent =
null
;
DownFileThread downFileThread;
//自定义文件下载线程
private
Handler updateHandler =
new
Handler(){
@Override
public
void
handleMessage(Message msg) {
switch
(msg.what){
case
DOWNLOAD_COMPLETE:
//点击安装PendingIntent
Uri uri = Uri.fromFile(downFileThread.getApkFile());
Intent installIntent =
new
Intent(Intent.ACTION_VIEW);
installIntent.setDataAndType(uri,
"application/vnd.android.package-archive"
);
updatePendingIntent = PendingIntent.getActivity(DownloadServices.
this
,
0
, installIntent,
0
);
myNotification.changeContentIntent(updatePendingIntent);
myNotification.notification.defaults=Notification.DEFAULT_SOUND;
//铃声提醒
myNotification.changeNotificationText(
"下载完成,请点击安装!"
);
//停止服务
// myNotification.removeNotification();
stopSelf();
break
;
case
DOWNLOAD_FAIL:
//下载失败
// myNotification.changeProgressStatus(DOWNLOAD_FAIL);
myNotification.changeNotificationText(
"文件下载失败!"
);
stopSelf();
break
;
default
:
//下载中
Log.i(
"service"
,
"default"
+msg.what);
// myNotification.changeNotificationText(msg.what+"%");
myNotification.changeProgressStatus(msg.what);
}
}
};
public
DownloadServices() {
// TODO Auto-generated constructor stub
// mcontext=context;
Log.i(
"service"
,
"DownloadServices1"
);
}
@Override
public
void
onCreate() {
// TODO Auto-generated method stub
Log.i(
"service"
,
"onCreate"
);
super
.onCreate();
}
@Override
public
void
onDestroy() {
// TODO Auto-generated method stub
Log.i(
"service"
,
"onDestroy"
);
if
(downFileThread!=
null
)
downFileThread.interuptThread();
stopSelf();
super
.onDestroy();
}
@Override
public
int
onStartCommand(Intent intent,
int
flags,
int
startId) {
// TODO Auto-generated method stub
Log.i(
"service"
,
"onStartCommand"
);
updateIntent =
new
Intent(
this
, MainActivity.
class
);
PendingIntent updatePendingIntent = PendingIntent.getActivity(
this
,
0
,updateIntent,
0
);
myNotification=
new
MyNotification(
this
, updatePendingIntent,
1
);
// myNotification.showDefaultNotification(R.drawable.ic_launcher, "测试", "开始下载");
myNotification.showCustomizeNotification(R.drawable.ic_launcher,
"测试下载"
, R.layout.notification);
filePathString=Environment.getExternalStorageDirectory().getAbsolutePath() +
"/family.apk"
;
//开启一个新的线程下载,如果使用Service同步下载,会导致ANR问题,Service本身也会阻塞
downFileThread=
new
DownFileThread(updateHandler,
"http://10.103.241.247:8013/update/download"
,filePathString);
new
Thread(downFileThread).start();
return
super
.onStartCommand(intent, flags, startId);
}
@Override
@Deprecated
public
void
onStart(Intent intent,
int
startId) {
// TODO Auto-generated method stub
Log.i(
"service"
,
"onStart"
);
super
.onStart(intent, startId);
}
@Override
public
IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
Log.i(
"service"
,
"onBind"
);
return
null
;
}
}
|