根据功能模块划分(Android开发推荐此方法)
- Activity mobilesafe.activty - 后台服务 mobilesafe.service - 广播接受者 mobilesafe.receiver - 数据库 mobilesafe.db.dao - 对象(java bean) mobilesafe.domain/bean - 自定义控件 mobilesafe.view - 工具类 mobilesafe.utils - 业务逻辑 mobilesafe.engine
闪屏页面(Splash)作用:
- 展示logo,公司品牌- 项目初始化,数据库copy- 检测版本更新- 校验程序合法性(比如:判断是否有网络,有的话才运行)
AndroidMinifest.xml 四大组件都需要在这里配置
1 2//版本名 6 7 10 //项目所需的权限11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 //主题 //activity的注册32 35 //起始的activity36 40 4138 39 42 43 44 4546 4748 4950 5152 5354 5556 5758 5960 6162 6364 6566 6768 69 70 //广播接收者的 注册71 72 7673 7574 77 81 88 //服务的注册8978 8079 90 9192 9394 95 96 97
activity_splash.xml
去除标题栏
1.在values目录的styles.xml文件中增加属性 <!-- Application theme. --> <style name="AppTheme" parent="AppBaseTheme"> <!-- 去除标题栏 --> <item name="android:windowNoTitle">true</item> <!-- All customizations that are NOT specific to a particular API-level can go here. --> </style> 2.在清单文件中的application中设置theme属性是我们工程的AppTheme android:theme="@style/AppTheme"
SplashActivity.java
1 public class SplashActivity extends Activity { 2 3 protected static final int CODE_UPDATE_DIALOG; 4 protected static final int CODE_URL_ERROR; 5 protected static final int CODE_NET_ERROR; 6 protected static final int CODE_JSON_ERROR; 7 protected static final int CODE_ENTER_HOME; 8 9 private TextView tvVersion; 10 private TextView tvProgress;// 下载进度展示 11 // 服务器返回的信息 12 private String mversionName;// 版本名 13 private int mversionCode;// 版本号 14 private String mDesc;// 版本描述 15 private String mdowmloadurl;// 下载地址 16 17 private Handler mHandler = new Handler() { 18 public void handleMessage(android.os.Message msg) { 19 switch (msg.what) { 20 case CODE_UPDATE_DIALOG: 21 showUpdateDialog();//显示升级对话框 22 break; 23 case CODE_URL_ERROR: 24 Toast.makeText(SplashActivity.this, "url错误", Toast.LENGTH_SHORT).show(); 25 enterHome(); 26 break; 27 case CODE_NET_ERROR: 28 Toast.makeText(SplashActivity.this, "网络错误", Toast.LENGTH_SHORT).show(); 29 enterHome(); 30 break; 31 case CODE_JSON_ERROR: 32 Toast.makeText(SplashActivity.this, "json数据解析解析错误", Toast.LENGTH_SHORT).show(); 33 enterHome(); 34 break; 35 case CODE_ENTER_HOME: 36 enterHome(); 37 break; 38 default: 39 break; 40 } 41 }; 42 }; 43 private SharedPreferences sp; 44 private RelativeLayout rlRoot; 45 46 @Override 47 protected void onCreate(Bundle savedInstanceState) { 48 super.onCreate(savedInstanceState); 49 setContentView(R.layout.activity_splash); 50 51 tvVersion = (TextView) findViewById(R.id.tv_version); 52 tvProgress = (TextView) findViewById(R.id.tv_progress);// 默认隐藏 53 tvVersion.setText("版本号:" + getVersionCode());//给版本号设置内容,动态获取的值 54 55 rlRoot = (RelativeLayout) findViewById(R.id.rl_root); 56 57 58 //判断是否需要自动更新 59 sp = getSharedPreferences("config", MODE_PRIVATE); 60 boolean autoUpdate = sp.getBoolean("auto_update", true); 61 62 copyDB("address.db");//拷贝归属地查询数据库 63 copyDB("antivirus.db");//拷贝病毒库 64 //更新病毒库 65 updateVirus(); 66 67 if(autoUpdate){ 68 checkVersion(); 69 }else{ 70 mHandler.sendEmptyMessageDelayed(CODE_ENTER_HOME, 2000);//发送一个延时2s的消息 71 } 72 73 //闪屏页渐变动画效果 74 AlphaAnimation anim = new AlphaAnimation(0.3f, 1f);//从哪个度数到哪个度数。。。是0-1的值。从0.3的透明度到完全不透明 75 anim.setDuration(2000);//延时2s 76 rlRoot.startAnimation(anim); 77 } 78 79 80 //更新病毒数据库 81 private void updateVirus() { 82 //联网从服务器获取到最近数据的MD5的特征码 83 HttpUtils httputils = new HttpUtils(); 84 String url = "http://172.28.3.112:8080/virus.json"; 85 httputils.send(HttpMethod.GET, url, new RequestCallBack(){ 86 87 @Override 88 public void onFailure(HttpException arg0, String arg1) { 89 // TODO Auto-generated method stub 90 91 } 92 93 @Override 94 public void onSuccess(ResponseInfo arg0) { 95 // TODO Auto-generated method stub 96 //System.out.println(arg0.result); 97 98 // JSONObject jsonobject = new JSONObject(arg0.result); 99 // String md5 = jsonobject.getString("md5");100 // String desc = jsonobject.getString("desc");101 102 }103 104 105 106 });107 108 }109 110 111 112 // 获取本地版本号113 private int getVersionCode() {114 PackageManager packgeManager = getPackageManager();//拿到包的管理者。。包管理器,获取手机里面每个apk的信息(清单文件信息)115 try { // 获取包的信息。。 getPackageName()当前应用程序的包名 等于 package="com.mxn.mobilesafe" //根据包名获取清单文件中的信息,其实就是返回一个保存有清单文件信息的javabean //packageName :应用程序的包名 //flags : 指定信息的标签,0:获取基础的信息,比如包名,版本号,要想获取权限等等信息,必须通过标签来指定,才能去获取 //GET_PERMISSIONS : 标签的含义:处理获取基础信息之外,还会额外获取权限的信息 //getPackageName() : 获取当前应用程序的包名116 PackageInfo packageInfo = packgeManager.getPackageInfo(getPackageName(), 0);117 int versionCode = packageInfo.versionCode;118 String versionName = packageInfo.versionName;119 System.out.println("versionname=" + versionName + ";" + "versioncode=" + versionCode);120 return versionCode;121 } catch (NameNotFoundException e) {122 // 没有找到包名时123 e.printStackTrace();124 }125 return -1;126 127 } 128 //连接服务器129 // 从服务器获取版本信息进行校验130 private void checkVersion() {131 final long startTime = System.currentTimeMillis();132 133 new Thread() { // 网络访问在分线程异步加载数据 1.连接服务器,查看是否有最新版本, 联网操作,耗时操作,4.0以后不允许在主线程中执行的,放到子线程中执行134 public void run() {135 Message msg = Message.obtain();136 HttpURLConnection con = null;137 try { // 本机地址:localhost 如果用模拟器加载本机的地址:用10.0.0.2来替换 //1.1连接服务器 //1.1.1设置连接路径 //spec:连接路径138 URL url = new URL("http://10.0.2.2:8080/update.json");139 1.1.2获取连接操作140 con = (HttpURLConnection) url.openConnection();//http协议,httpClient //1.1.3设置请求方式141 con.setRequestMethod("GET");//设置请求方法 //1.1.4设置超时时间142 con.setConnectTimeout(5000);// 设置连接超时,5S143 con.setReadTimeout(5000);// 设置响应超时,链接上了,但服务器迟迟没有响应144 con.connect();// 链接服务器145 //1.1.5获取服务器返回的状态码,200,404,500146 int responseCode = con.getResponseCode();//获取响应码147 if (responseCode == 200) { // 解析json148 //1.获取服务器返回的流信息149 InputStream inputStream = con.getInputStream();150 // 2.流转化为字符串151 String result = StreamUtils.readFormStream(inputStream);//自己定义的StreamUtils工具类152 System.out.println("网络结果返回:" + result);153 //3.result是一个json字符串,进行解析154 155 JSONObject jo = new JSONObject(result); //4.获取数据156 mversionName = jo.getString("versionName");//拿到服务器端的版本名157 mversionCode = jo.getInt("versionCode");//拿到服务器端的版本号158 mDesc = jo.getString("description");//拿到服务器端的版本描述159 mdowmloadurl = jo.getString("downloadUrl");//拿到服务器端的下载链接160 161 System.out.println(mDesc);162 System.out.println(mversionCode);163 // 服务器的大于 本地的,判断是否有更新,如果大于 则有更新需要更新,弹出升级对话框164 if (mversionCode > getVersionCode()) {165 System.out.println("进行比较,有版本更新");166 msg.what = CODE_UPDATE_DIALOG;167 // showUpdateDialog();//这句是在子线程更新界面,android不能在子线程更新界面,要想在子线程更新界面所以用到handler.168 } else { // 如果没有版本更新169 msg.what = CODE_ENTER_HOME;170 }171 }172 } catch (MalformedURLException e) { // url错误的异常173 msg.what = CODE_URL_ERROR;174 e.printStackTrace();175 } catch (IOException e) { //网络错误异常176 // 这个是可以携带数据的msg.obj =177 msg.what = CODE_NET_ERROR;// what只是一个标识,用来区分消息!178 e.printStackTrace();179 } catch (JSONException e) { // json解析失败180 181 msg.what = CODE_JSON_ERROR;182 e.printStackTrace();183 } finally { //处理对话框延迟显示的问题184 long endTime = System.currentTimeMillis();185 long timeUsed = endTime - startTime;// 访问网络花费的时间186 if (timeUsed < 2000) {187 try { // 强制休眠2s,保证闪屏页面2S188 Thread.sleep(2000 - timeUsed);189 } catch (InterruptedException e) {190 // TODO Auto-generated catch block191 e.printStackTrace();192 }193 }194 195 mHandler.sendMessage(msg);// 消息发送出去,在handlemessage里进行相应的处理196 if (con != null) {197 con.disconnect();198 }199 200 }201 }202 203 }.start();204 205 }206 //升级对话框207 private void showUpdateDialog() {208 System.out.println("正在升级对话框");209 // 升级对话框210 AlertDialog.Builder builder = new AlertDialog.Builder(this);//context对象211 builder.setTitle("最新版本" + mversionName);212 builder.setMessage(mDesc);213 // builder.setCancelable(false);//不让用户取消对话框,用户体验太差214 builder.setPositiveButton("立即更新", new OnClickListener() {215 @Override216 public void onClick(DialogInterface dialog, int which) {217 // TODO Auto-generated method stub218 System.out.println("立即更新");219 // download方法220 download();221 }222 });223 builder.setNegativeButton("以后再说", new OnClickListener() {224 @Override225 public void onClick(DialogInterface dialog, int which) {226 enterHome();227 }228 });229 builder.setOnCancelListener(new OnCancelListener() {230 // 设置取消监听,用户点击返回键时触发231 @Override232 public void onCancel(DialogInterface dialog) {233 enterHome();234 }235 });236 builder.show();237 } 238 239 protected void download() { // 下载服务器端的apk文件240 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {241 // 判断是否有sd卡,sd卡挂载的时候才可以242 tvProgress.setVisibility(View.VISIBLE);// 显示进度243 244 String target = Environment.getExternalStorageDirectory() + "/update.apk";//把文件下载到哪个路径下,sd卡的根目录245 // xutils框架,使用HttpUtils工具下载文件,下载一个jar包246 HttpUtils utils = new HttpUtils();247 utils.download(mdowmloadurl, target, new RequestCallBack () {248 @Override // 文件下载进度249 public void onLoading(long total, long current, boolean isUploading) {250 // TODO Auto-generated method stub251 super.onLoading(total, current, isUploading);252 System.out.println("下载进度:" + current + "/" + total);253 tvProgress.setText("下载进度:" + current * 100 / total + "%");254 }255 256 @Override257 public void onSuccess(ResponseInfo arg0) {258 // TODO Auto-generated method stub259 Toast.makeText(SplashActivity.this, "下载成功", Toast.LENGTH_SHORT).show();260 // 下载完成之后,跳到系统的安装界面。。Intent.ACTION_VIEW 是xml的action 标签 /* */ 261 Intent intent = new Intent(Intent.ACTION_VIEW);//系统的安装界面262 intent.addCategory(Intent.CATEGORY_DEFAULT);263 intent.setDataAndType(Uri.fromFile(arg0.result),264 "application/vnd.android.package-archive"); //在当前activity退出的时候,会调用之前的activity的onActivityResult方法 //requestCode : 请求码,用来标示是从哪个activity跳转过来 //ABC a -> c b-> c ,c区分intent是从哪个activity传递过来的,这时候就要用到请求码265 // startActivity(intent);266 startActivityForResult(intent, 0);// 如果用户取消安装,会返回结果,回调方法onActivityResult,下文定义267 }268 269 @Override270 public void onFailure(HttpException arg0, String arg1) {271 // TODO Auto-generated method stub272 Toast.makeText(SplashActivity.this, "下载失败", Toast.LENGTH_SHORT).show();273 }274 });275 } else {276 Toast.makeText(SplashActivity.this, "没有SD卡", Toast.LENGTH_SHORT).show();277 }278 }279 280 @Override//用户取消安装,回调此方法281 protected void onActivityResult(int requestCode, int resultCode, Intent data) {282 // TODO Auto-generated method stub283 System.out.println("出现安装界面,用户点击取消时。");284 enterHome();285 super.onActivityResult(requestCode, resultCode, data);286 }287 288 private void enterHome() { // 进入主界面289 Intent intent = new Intent(this, HomeActivity.class);290 startActivity(intent);291 finish();292 }293 294 295 296 297 //拷贝数据库,从assets目录下拷贝到data/data/com.mxn.mobilesafe/files目录下298 private void copyDB(String dbName){299 //获取文件路径300 File destFile = new File(getFilesDir(),dbName);301 302 if(destFile.exists()){303 System.out.println("已存在");304 }305 306 307 FileOutputStream out = null;308 InputStream in = null;309 310 try {311 in = getAssets().open(dbName);312 313 out = new FileOutputStream(destFile);314 int len = 0; 315 byte[] buffer = new byte[1024];316 317 while((len = in.read(buffer))!=-1){318 out.write(buffer,0,len);319 }320 321 322 } catch (IOException e) {323 // TODO Auto-generated catch block324 e.printStackTrace();325 }finally{326 try {327 in.close();328 out.close();329 } catch (IOException e) {330 // TODO Auto-generated catch block331 e.printStackTrace();332 }333 334 }335 336 }337 } //content : 从内容提供者中获取数据 content:// // file : 从文件中获取数据
StreamUtils.java
1 /* 2 * 读取流的工具 3 * 把流对象转换成字符串对象 4 */ 5 public class StreamUtils { 6 //将输入流读取成String后返回 7 public static String readFormStream(InputStream in) throws IOException{ 8 // 定义字节数组输出流对象 9 ByteArrayOutputStream out = new ByteArrayOutputStream();10 // 定义读取的长度 11 int len = 0 ;12 // 定义读取的缓冲区13 byte[] buffer = new byte[1024];14 // 按照定义的缓冲区进行循环读取,直到读取完毕为止 15 while((len=in.read(buffer))!=-1){16 // 根据读取的长度写入到字节数组输出流对象中 17 out.write(buffer,0,len); 18 }19 String result = out.toString();20 // 关闭流 21 in.close();22 out.close();23 return result;24 // // 把读取的字节数组输出流对象转换成字节数组 25 // byte data[] = out.toByteArray(); 26 // // 按照指定的编码进行转换成字符串(此编码要与服务端的编码一致就不会出现乱码问题了,android默认的编码为UTF-8) 27 // return new String(data, "UTF-8"); 28 29 }30 31 }
系统安装界面的activity的配置:
我们服务器用的是tomcat,里面放置 新版本的apk和update.json:
将代码打包为apk文件:
涉及的知识点:
PackageManager 包管理器,获取手机里面每个apk的信息(清单文件信息)
版本更新流程:
网络请求
> * URL> * HttpUrlConntetionJSON解析
> * JSONObject 专门用来解析json> * JSONArray对话框弹出
> AlertDialog> AlertDialog.Builder
子线程更新UI
> * Handler + message> * runOnUiThread(runnable)
页面之间的跳转Intent
GitHub 一个开源的网站,下载xUtils框架,将下载的jar包导入工程。
AlertDialog.Builder(this)
子类拥有父类的所有方法, 而且可以有更多自己的方法。父类无法有子类的方法
Activity(token), Context(没有token)平时,要获取context对象的话, 优先选择Activity, 避免bug出现, 尽量不用getApplicationContext()activity是context的子类