Android 用service实现不依赖activity的版本更新功能

    做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

//构建系统对话框,注意第一个参数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键点击并不好用,对话框没消失,也没回到主界面。我估计是三星改了底层代码导致的。其他手机一切正常。就不上效果图了。希望对有需要的人有帮助。

你可能感兴趣的:(Android 用service实现不依赖activity的版本更新功能)