做Android开发的同学可能都很熟悉service了,四大组件之一吗,在后台运行没那么容易被杀死,可以处理一些不用显示在前台界面操作和功能。比如,音乐播放器的后台实现,网络下载等工作。我要写的是,关于app的版本检测和更新功能在service里的实现。
先介绍下项目背景。大部分app都需要提供一个在线更新的功能,一般有自动弹出对话框,或者点击某个按钮后,弹出对话框提示有新版本的更新。一般来说,我们会在app的第一个activity的oncreate方法里,开启一条线程,去访问服务器,得到一个json或者xml数据,然后解析,判断是否有新版本。如果有,弹出对话框提示用户。好像没什么问题,开始我也是这么做的。后来,出现了问题后才使用现在的解决方案的。
我们设想一下,这样的流程中弹出的对话框,是依赖于这个界面的,也就是这个activity的,对吧,而网络访问,解析,是耗时的操作,如果,用户每次在你的更新界面不做多的停留,那么他是不是永远都不知道有新版本了?再想的糟糕点,如果这个界面跟欢迎界面一样,就只出现一次,跳过了就finish掉了,或者是被系统回收了。那就不是看不到这个对话框了,你的程序会崩掉。因为你的dialog依赖于这个activity。怎么样能较好的处理上述几个问题呢。没错,这个时候你可以考虑系统对话框,完全独立的对话框,不依赖于activity。甚至,你都不要在activity里去调用这个系统对话框,让她和活动彻底分手。
你可以写一个继承自Application的类,在这个类里来操作和控制dialog。但是控制力度没有在service里来操作更方便自如。废话有点多了。说说具体要实现的东西。在service里,开启网络访问下载文件,这里我用的是okhttp框架。解析json数组,版本号小于解析的版本信息,就弹出对话框。上代码:
package net.xprinter.utils;
import java.io.File;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import net.xprinter.R;
import net.xprinter.entity.VersionEntity;
import net.xprinter.testview.HomeListener;
import net.xprinter.testview.HomeListener.OnHomePressedListener;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.Service;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.view.WindowManager;
import android.widget.Toast;
public class DownLoadService extends Service{
public static boolean isRunning=false;
//ok3的构建对象方法
private static OkHttpClient client=new OkHttpClient.Builder().connectTimeout(6, TimeUnit.SECONDS).build();
private HomeListener mHomeListener;
Handler mHandler;
Dialog dialog;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
//Log.i("TAG","onCreateservice");
super.onCreate();
//此处代码是监听home键用的,系统会话框在按下home后并不会消失,监听home键,调用dialog.cancle;
mHomeListener=new HomeListener(getApplicationContext());
mHomeListener.setOnHomePressedListener(new OnHomePressedListener() {
@Override
public void onHomePressed() {
// TODO Auto-generated method stub
if (dialog!=null) {
dialog.cancel();
}
}
@Override
public void onHomeLongPressed() {
// TODO Auto-generated method stub
if (dialog!=null) {
dialog.cancel();
}
}
});
mHomeListener.startWatch();
//对于异步网络任务,回调方法里与主线程交互用的handler
mHandler=new Handler(){
public void handleMessage(android.os.Message msg) {
int msgid=msg.what;
Bundle bundle=msg.getData();
switch (msgid) {
case UpdataBiz.version_error://网络请求onfailed回调
Toast.makeText(getApplicationContext(), R.string.genxinshibai, 0).show();
stopSelf();
break;
case UpdataBiz.error_msg://网络请求onfailed回调
Toast.makeText(getApplicationContext(), R.string.genxinshibai, 0).show();
stopSelf();
break;
case UpdataBiz.version_msg://网络访问成功,handler比较版本
//json解析的版本信息实体类
final VersionEntity entity=(VersionEntity) bundle.getSerializable("version");
//得到当前版本号的方法
try {
PackageManager manager=getPackageManager();
String packageName=getPackageName();
PackageInfo packageInfo;
packageInfo = manager.getPackageInfo(packageName, 0);
if (packageInfo.versionCode<Integer.parseInt(entity.getVersion())) {
//构建系统对话框,注意第一个参数context,不再与activity有关,第二个是dialog样式
Builder builder=new Builder(getApplicationContext(),R.style.dialog);
builder.setIcon(android.R.drawable.btn_star);
builder.setTitle(R.string.BMUpdateTitle);
builder.setMessage(entity.getChangeLog());
builder.setPositiveButton(R.string.BMUpdateNow, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
//biz类的下载方法
UpdataBiz.downLoad(mHandler, client, entity.getApkUrl());
}
});
builder.setNeutralButton(R.string.BMNotNow, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
stopSelf();//不下载,就关掉服务service,避免占用资源
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
//重写dialog的cancle方法,dialog消失,服务关闭,写不写随你需要而定
@Override
public void onCancel(DialogInterface dialog) {
// TODO Auto-generated method stub
stopSelf();
}
});
dialog=builder.create();
//系统dialog最终要的一行代码,还有需要一个权限android.permission.SYSTEM_ALERT_WINDOW,不然会报错 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
}else {
Toast.makeText(getApplicationContext(), R.string.no_updata, 0).show();
stopSelf();
}
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case UpdataBiz.download_msg:
//将下载的文件执行安装,并关闭服务
String apkPath=bundle.getString("apkpath");
File file=new File(apkPath);
Intent intent=new Intent(Intent.ACTION_VIEW);
Uri uri=Uri.fromFile(file);
String type="application/vnd.android.package-archive";
intent.setDataAndType(uri, type);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
stopSelf();
break;
default:
break;
}
};
};
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
//Log.i("TAG","onStartservice");
isRunning=true;
//biz类里的获取版本信息的网络请求
UpdataBiz.getNewVersion(mHandler, client);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
isRunning=false;
//home键的监听关闭,home键在2.3版本后要用广播才能监听,这个封装好的home监听类是网上找的,源码也会贴上,不过注明这个不是自己写的。
mHomeListener.stopWatch();
//Log.i("TAG", "onDestroyserver");
}
}
执行网络请求的biz类:
package net.xprinter.utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import com.alibaba.fastjson.JSON;
import net.xprinter.entity.VersionEntity;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class UpdataBiz {
//handler发消息的几个常量
public static final int error_msg=0;
public static final int version_msg=1;
public static final int download_msg=2;
public static final int version_error=4;
public static void downLoad(final Handler handler,OkHttpClient client,String url){
//ok请求的写法,因为不是介绍okhttp,就不多注释了
Request request=new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
final String target="/mnt/sdcard/"+UUID.randomUUID().toString()+".apk";
//访问失败
@Override
public void onFailure(Call arg0, IOException arg1) {
// TODO Auto-generated method stub
Message message=handler.obtainMessage();
message.what=error_msg;
handler.sendMessage(message);
}
//请求成功得到文件流,写入到指定文件
@Override
public void onResponse(Call arg0, Response response) throws IOException {
// TODO Auto-generated method stub
Log.i(“TAG”,”“+response.body().contentLength());
InputStream is=null;
FileOutputStream fo=null;
byte[] b=new byte[1024*2];
int len=0;
try {
is=response.body().byteStream();
File f=new File(target);
fo=new FileOutputStream(f);
while ((len=(is.read(b)))!= -1) {
fo.write(b,0,len);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if (is!=null) {
is.close();
}
if (fo!=null) {
fo.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//faxiaoxi
Message message=handler.obtainMessage();
message.what=download_msg;
Bundle bundle=new Bundle();
bundle.putString("apkpath", target);
message.setData(bundle);
handler.sendMessage(message);
}
});
}
//请求获得版本信息
public static void getNewVersion(final Handler handler,OkHttpClient client){
String url="http://www.xprinter.net/upload/apkinfo.txt";
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call arg0, IOException arg1) {
// TODO Auto-generated method stub
Message message=handler.obtainMessage();
message.what=version_error;
handler.sendMessage(message);
}
@Override
public void onResponse(Call arg0, Response response) throws IOException {
// TODO Auto-generated method stub
String json=response.body().string();
//json=new String(json.getBytes("ANSI"),"UTF-8");
Log.i("TAG", json);
//瑙f瀽json
Message message=handler.obtainMessage();
VersionEntity entity=new VersionEntity();
try {
entity = JSON.parseObject(json, VersionEntity.class);
Log.i("TAG", entity.getApkUrl());
message.what=version_msg;
Bundle bundle =new Bundle();
bundle.putSerializable("version", entity);
message.setData(bundle);
handler.sendMessage(message);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
message.what=version_error;
handler.sendMessage(message);
}
}
});
}
}
//监听home键的类:
此类为网络上搬来的,不是本人原创
package net.xprinter.testview;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
/**
* Home键监听封装
*
*
*/
public class HomeListener {
static final String TAG = "HomeListener";
private Context mContext;
private IntentFilter mFilter;
private OnHomePressedListener mListener;
private InnerRecevier mRecevier;
// 回调接口
public interface OnHomePressedListener {
public void onHomePressed();
public void onHomeLongPressed();
}
public HomeListener(Context context) {
mContext = context;
mFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
}
/**
* 设置监听
*
* @param listener
*/
public void setOnHomePressedListener(OnHomePressedListener listener) {
mListener = listener;
mRecevier = new InnerRecevier();
}
/**
* 开始监听,注册广播
*/
public void startWatch() {
if (mRecevier != null) {
mContext.registerReceiver(mRecevier, mFilter);
}
}
/**
* 停止监听,注销广播
*/
public void stopWatch() {
if (mRecevier != null) {
mContext.unregisterReceiver(mRecevier);
}
}
/**
* 广播接收者
*/
class InnerRecevier extends BroadcastReceiver {
final String SYSTEM_DIALOG_REASON_KEY = "reason";
final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
final String SYSTEM_DIALOG_REASON_ASSIST = "assist";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (reason != null) {
// Log.e(TAG, “action:” + action + “,reason:” + reason);
if (mListener != null) {
if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
// 短按home键
mListener.onHomePressed();
} else if (reason
.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
// 长按home键
mListener.onHomeLongPressed();
} else if (SYSTEM_DIALOG_REASON_ASSIST.equals(reason)) {
// samsung 长按Home键
mListener.onHomeLongPressed();
}
}
}
}
}
}
}
好了,这样的话,你只需要在你需要启动服务的地方,startservice()就可以开启服务了。执行你要的功能,并能很好的隔离出这个功能,不依赖于界面。注意:service里的代码也是运行在主线程的,要注意多线程的使用。最后说一下我的实现后的效果。当系统对话框弹出的时候,我的欢迎界面还未结束,欢迎界面结束后跳转到另一界面,而这个系统对话框完全不受影响。
也有一个bug,分享出来,在三星s7上,home键点击并不好用,对话框没消失,也没回到主界面。我估计是三星改了底层代码导致的。其他手机一切正常。就不上效果图了。希望对有需要的人有帮助。