【Android 应用开发】Android应用的自动更新模块
.
作者 :万境绝尘
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835
.
软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很流行使用Splash界面, 正好与自动更新配套在一起;
在这个自动更新Splash中, 使用到了 动画设置 ,SharedPerference ,pull解析 ,dialog对话框 ,http网络编程 ,handler 等.
注意一个错误 : 已安装具有该名称和不同签名的数据包 , 早上测试人员报告突然出现这个问题, 在开发的时候我直接将eclipse上编译的版本放到了服务器上, 最后出现了这个问题, 开发的时候明明是好的啊, 怎么测试的时候出问题了呢.
编译环境不同, 产生的签名是不一样的, 在eclipse上编译生成 与 正式版本在linux下编译 所产生的 数字签名 是不一样的.
又发现一个BUG : 在弹出更新对话框, 点击确定下载完毕之后会弹出系统自带的替换应用程序对话框, 在这里点取消的话就会一直卡在Splash界面. 设置一个跳转机制解决这个问题.
解决方案 :利用触摸划屏事件, 向左侧划屏100px, 就自动跳转到主界面 , 最后的最终代码已经加上去了
/*** 设置触摸事件* 在手指按下时记录x坐标值 , 在手指抬起的时候记录x坐标值 , 如果两个值相差超过100* 那么跳转到主界面 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)*/@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN :touchPositionX0 = (int) event.getX();break;case MotionEvent.ACTION_UP :touchPositionX1 = (int) event.getX();if((touchPositionX0 - touchPositionX1) > 100)loadMainUI();touchPositionX0 = 0;touchPositionX1 = 0;break;}return true;}
一. 创建Activity
1. 创建Activity大概流程
a. 设置全屏显示.
b. 设置布局, 并在布局中显示当前版本号, 为Splash界面添加动画.
c. 获取当前时间.
d. 获取SharedPerence配置文件.
e. 开启检查版本号线程, 后续的操作都在这个线程中执行.
2. 设置窗口样式
(1) 设置全屏显示
a. 代码实现 : 由于是Splash界面, 这里需要设置成无标题, 并且全屏显示, 注意下面的两行代码需要在setContentView()方法之前调用;
//隐藏标题栏requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏状态栏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
b. 配置实现 :
AndroidManifest.xml
<activity android:name="myAcitivty" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
(2) 关于窗口的其它设置
//①设置窗体始终点亮
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//②设置窗体始终点亮
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
设置窗体始终点亮的配置文件实现
//③AndroidManifest.xml添加权限
<uses-permission android:name="android.permission.WAKE_LOCK" />
//设置窗体背景模糊
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
(3) 屏幕方向设置
a. 配置文件实现
//设置横屏
<activity android:name="myAcitivty" android:screenOrientation="landscape" /> //设置竖屏
<activity android:name="myAcitivty" android:screenOrientation="portrait" />
b. 代码实现
//设置横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//设置竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
c. 获取屏幕方向
//获取横屏方向
int orientation = this.getResources().getConfiguration().orientation;
其中的orientation方向可以使 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 或者 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE .
3. 设置动画
为了更好的用户体验, 这里给Splash界面添加一个动画, 这个动画加给整个界面.
(1) 创建动画
AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f); //创建动画
animation.setDuration(2000); //设置渐变
splash_rl.setAnimation(animation); //设置动画载体
创建动画吧: 创建的这个动画是透明度渐变动画, 传入浮点型参数, 0代表完全透明, 1代表不透明, 传入参数代表透明度从完全透明到不透明.
设置时间 : 设置的duration是动画渐变过程所消耗的时间.
设置动画 : 最后使用setAnimation()方法将穿件的动画设置给Splash界面.
(2) 动画常用方法
a. 普通设置
alphaAnimation.setRepeatCount(5);//设置重复次数alphaAnimation.setFillAfter(true);//动画执行完是否停留在执行完的状态alphaAnimation.setStartOffset(1000);//动画执行前等待的时间, 单位是毫秒alphaAnimation.start();//开始动画
b. 设置监听器
alphaAnimation.setAnimationListener(new AnimationListener() {//动画开始时回调@Overridepublic void onAnimationStart(Animation animation) {}//动画重复执行时回调@Overridepublic void onAnimationRepeat(Animation animation) {}//动画执行结束时回调@Overridepublic void onAnimationEnd(Animation animation) {}});
4. SharedPerference使用
//获取SharedPerferenceSharedPreferences sharedPreferences = getSharedPreferences("sp", Context.MODE_PRIVATE);Editor editor = sharedPreferences.edit(); //获取Editor对象editor.putBoolean("isUpdate", true); //向sp中写入数据editor.commit(); //提交sharedPreferences.getBoolean("isUpdate", true);//获取sp中的变量
5. onCreate()方法代码
/*** 创建Activity时调用* * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题* ② 设置布局, 版本号, 执行动画 * ③ 设置当前时间* ④ 获取SharedPerference配置文件* ⑤ 开启检查版本号线程, 后续操作都在改线程中操作* */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//隐藏标题栏requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏状态栏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置布局setContentView(R.layout.splash);/** 显示当前软件的版本号* 获取布局中的TextView控件, 将版本号设置到这个TextView控件中*/tv_version = (TextView) findViewById(R.id.tv_version);version =getString(R.string.current_version) + " " + getVersion();tv_version.setText(version);/** 在界面设置一个动画, 用来表明正在运行* a. 获取布局* b. 创建一个动画对象* c. 将动画设置到布局中*/splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);animation.setDuration(2000);splash_rl.setAnimation(animation);/** 这个时间值是用来控制Splash界面显示时间的* 记录下这个值, 然后执行到下面, 如果时间差在3秒以内, * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差* 等够3秒在执行下面的操作*/time = System.currentTimeMillis();//从SharedPreference中获取一些配置sp = getSharedPreferences("config", Context.MODE_PRIVATE);//开启检查版本号线程new Thread(new CheckVersionTask()).start();}
二. 检查版本号
1. 检查版本号线程
流程 :
a. 保持Splash持续时间 : 获取当前时间与time进行比较, 如果不足3秒, 人为使Splash保持3秒时间;
b. 查看更新设置 : 从sp中获取更新设置, 如果sp中自动更新为true, 那么就执行下面的更新流程, 如果sp中自动更新为false, 那么直接进入主界面.
c. 获取信息 : 从网络中获取更新信息, 根据是否成功获取信息执行不同的操作.
源码 :
private final class CheckVersionTask implements Runnable{public void run() {try {/** 获取当前时间, 与onCreate方法中获取的时间进行比较* 如果不足3秒, 在等待够3秒之后在执行下面的操作*/long temp = System.currentTimeMillis();if(temp - time < 3000){SystemClock.sleep(temp - time);}/** 检查配置文件中的设置, 是否设置了自动更新; * 如果设置了自动更新, 就执行下面的操作,* 如果没有设置自动更新, 就直接进入主界面*/boolean is_auto_update = sp.getBoolean("is_auto_update", true);if(!is_auto_update){loadMainUI();return;}/** 获取更新信息* 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作* 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作* 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作*/updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);if(updateInfo != null){Message msg = new Message();msg.what = SUCESS_GET_UPDATEINOF;mHandler.sendMessage(msg);}else{Message msg = new Message();msg.what = ERROR_GET_UPDATEINOF;mHandler.sendMessage(msg);}} catch (Exception e) {e.printStackTrace();Message msg = new Message();msg.what = ERROR_GET_UPDATEINOF;mHandler.sendMessage(msg);}}}
2. 获取版本号方法
流程 :
a. 创URL建对象;
b. 创建HttpURLConnection对象;
c. 设置超时时间;
d. 设置获取方式;
e. 查看链接是否成功;
f. 解析输入流信息;
源码 :
/*** 获取更新信息* ① 根据字符串地址创建URL对象* ② 根据URL对象创建HttpURLConnection链接对象* ③ 设置链接对象5秒超时* ④ 设置链接对象获取的方式为get方式* ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流* ⑥ 解析输入流获取更新信息* */private UpdateInfo getUpdateInfo(String path){try {URL url = new URL(path); //创建URL对象//创建连接对象HttpURLConnection conn = (HttpURLConnection) url.openConnection();//设置链接超时conn.setConnectTimeout(5000);//设置获取方式conn.setRequestMethod("GET");//如果连接成功, 获取输入流if(conn.getResponseCode() == 200){InputStream is = conn.getInputStream();//解析输入流中的数据, 返回更新信息return parserUpdateInfo(is);}} catch (MalformedURLException e) {e.printStackTrace();} catch (ProtocolException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}
3. 更新信息对象
将从网上获取的更新信息 包括 版本号, apk文件地址, 软件描述等信息封装在一个类中.
public class UpdateInfo {private String version; //当前软件版本号private String url; //获取到的软件地址private String description; //软件描述public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}@Overridepublic String toString() {return "UpdateInfo [version=" + version + ", url=" + url+ ", description=" + description + "]";}}
4. pull解析输入流
(1) pull解析流程
a. 获取pull解析器 : XmlPullParser parser = Xml.newPullParser();
b. 为pull解析器设置编码 : parser.setInput(inputStream, "UTF-8");
c. 获取pull解析器事件 : int eventType = parser.getEventType(), 之后的解析都要根据这个解析事件进行, 例如开始解析标签的事件时 XmlPullParser.START_TAG, 文档结束的事件时 XmlPullParser.END_DOCUMENT.
d. 解析流程控制 : 解析的时候, 如果没有解析到文档最后就一直解析, 这里使用while循环, eventType != XmlPullParser.END_DOCUMENT 就一直循环, 循环玩一个元素之后, 调用parser.next()遍历下一个元素.
e. 获取标签名 : 在事件解析标签的时候 ( eventType == XmlPullParser.START_TAG ) , 调用parser.getName()可以获取这个标签的标签名, 如果我们想要获取这个标签下的文本元素, 可以使用parser.nextText()来获取.
(2) 更新xml文件
<?xml version="1.0" encoding="UTF-8"?>
<updateInfo><version>3.2</version><url>http://127.0.0.1:8080/web/mobilesafe.apk</url><description>客户端更新</description>
</updateInfo>
(3) 源码
/*** 获取更新信息* ① 创建pull解析器* ② 为解析器设置编码格式* ③ 获取解析事件* ④ 遍历整个xml文件节点, 获取标签元素内容*/private UpdateInfo parserUpdateInfo(InputStream is){try {UpdateInfo updateInfo = null;//1. 创建pull解析解析器XmlPullParser parser = Xml.newPullParser();//2. 设置解析编码parser.setInput(is, "UTF-8");//3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等int eventType = parser.getEventType();//4. 在文档结束前一直解析while (eventType != XmlPullParser.END_DOCUMENT) {switch (eventType) {//只解析标签case XmlPullParser.START_TAG:if ("updateInfo".equals(parser.getName())) {//当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象updateInfo = new UpdateInfo();} else if ("version".equals(parser.getName())) {//解析版本号标签updateInfo.setVersion(parser.nextText());} else if ("url".equals(parser.getName())) {//解析url标签updateInfo.setUrl(parser.nextText());} else if ("description".equals(parser.getName())) {//解析描述标签updateInfo.setDescription(parser.nextText());}break;default:break;}//每解析完一个元素, 就将解析标志位下移eventType = parser.next();}is.close();return updateInfo;} catch (XmlPullParserException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}
三. Handler对象
Handler对象用来控制整个更新过程的进行;
private Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg) {switch (msg.what) {/** 获取更新信息错误 , 在断网或者获取信息出现异常执行* 提示一下, 之后进入主界面*/case ERROR_GET_UPDATEINOF:ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);loadMainUI();break;/** 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来* 如果版本号相同, 说明不用更新, 直接进入主界面* 如果版本号不同, 需要弹出更新对话框*/case SUCESS_GET_UPDATEINOF:if(updateInfo.getVersion().equals(version)){loadMainUI();}else{showUpdateDialog();}break;/** 下载apk文件出现错误, 中途断网 出现异常等情况* 提示后进入主界面*/case ERROR_DOWNLOAD_APK:mPb.dismiss();ToastHint.getInstance().showHint(R.string.fail_to_get_apk);loadMainUI();break;/** 成功下载apk文件之后执行的操作* 取消进度条对话框, 之后安装apk文件*/case SUCCESS_DOWNLOAD_APK:mPb.dismiss();installApk();break;default:break;}};};
.
作者 :万境绝尘
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835
.
四. 下载安装apk文件
1. 更新对话框
(1) 更新流程
先弹出更新对话框提示, 点击确定就弹出进度条对话框, 下载apk文件 . 如果点击取消, 直接进入主界面
更新对话框 : 这是一个AlertDialog , 先创建builder, 然后设置标题, 显示内容, 设置积极消极按钮, 创建对话框 之后显示对话框;
进度条对话框 : 这是一个ProgressDialog, 直接使用new创建, 设置信息与显示样式, 最后显示对话框.
(2) 创建对话框流程
创建一个对话框的流程 :
a. 创建builder对象 : Builder builder = new Builder(context);
b. 设置标题 : builder.setTittle("");
c. 设置显示信息 : builder.setMessage("");
d. 设置按钮 : builder.setPositiveButton("", onClickListener);
e. 创建对话框 : Dialog dialog = builder.create();
f. 显示对话框 : dialog.show();
创建进度条对话框流程 :
a. 创建进度条对话框 : ProgressDialog progressDialog = new ProgressDialog(context);
b. 设置进度条对话框样式 : progressDialog.setProgressStyle();
c. 设置显示信息 : progressDialog.setMessage();
d. 显示对话框 : progressDialog.show();
(3) 源码
/*** 弹出更新对话框* * a. 创建builder对象* b. 设置标题* c. 设置对话框显示信息* d. 设置该对话框不可回退, 如果回退的话就会卡在本界面* e. 设置确定按钮* f. 设置取消按钮* g. 创建对话框* h. 显示对话框* * 确定按钮按下显示进度条对话框* a. 创建一个进度条对话框* b. 设置该对话框不能回退* c. 设置进度条样式* d. 设置进度条的信息* e. 显示进度条对话框* f. 开启一个线程, 下载apk文件*/protected void showUpdateDialog() {//创建builder对象AlertDialog.Builder builder = new AlertDialog.Builder(this);//设置标题builder.setTitle(getString(R.string.update_dialog_tittle));//设置对话框信息builder.setMessage(updateInfo.getDescription());//设置不可回退builder.setCancelable(false);//设置确定按钮builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {//创建进度条对话框mPb = new ProgressDialog(SplashActivity.this);//设置进度条对话框不可回退mPb.setCancelable(false);//设置进度条对话框样式mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//设置进度条对话框的信息mPb.setMessage(getString(R.string.update_dialog_messsage));//显示进度条对话框mPb.show();//开启显示进度条对话框线程new Thread(new DownloadApkTask()).start();}});builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {loadMainUI();}});//创建更新信息提示对话框mUpdateInfoDialog = builder.create();//显示更新信息提示对话框mUpdateInfoDialog.show();}
2. 下载apk线程
/*** 在这个线程中主要执行downloadApk方法, 这个方法传入apk路径和进度条对话框* 注意 : 下载的前提是sd卡的状态是挂载的*/private final class DownloadApkTask implements Runnable{public void run() {if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){try {SystemClock.sleep(2000);apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);Message msg = new Message();msg.what = SUCCESS_DOWNLOAD_APK;mHandler.sendMessage(msg);} catch (Exception e) {e.printStackTrace();Message msg = new Message();msg.what = ERROR_DOWNLOAD_APK;mHandler.sendMessage(msg);}}}}
3. 下载apk核心方法
从网络下载文件流程 :
a. 创建URL对象 : 这个对象一般根据字符串地址创建, URL url = new URL(path);
b. 创建HttpURLConnection对象 : 这个对象根据URL对象创建, HttpURLConnection conn = (HttpURLConnection)url.openConnection();
c. 设置超时时间 : 单位是毫秒, conn.setConnectionTimeout(5000);
d. 设置请求方式 : conn.setRequestMethod("GET");
e. 成功连接 : 如果成功连接, 那么conn.getResponseCode()的值为200;
进度条对话框设置 :
a. 设置进度条最大值 : mProgressDialog.setMax(int max);
b. 设置进度条当前值 : mProgressDialog.setProgress(int curr);
/*** 下载apk更新文件* * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件* b. 创建URL对象* c. 创建HttpUrlConnection对象* d. 设置链接对象超时时间* e. 设置请求方式 get* f. 如果请求成功执行下面的操作* * g. 通过链接对象获取网络资源的大小* h. 将文件大小设置给进度条对话框* i. 获取输入流, 并且读取输入流信息* j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框*/public File downloadApk(String path,ProgressDialog pb) throws Exception{//创建本地文件对象File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));//创建HttpURL连接URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("GET");if(conn.getResponseCode() == 200){int max = conn.getContentLength();//设置进度条对话框的最大值pb.setMax(max);int count = 0;InputStream is = conn.getInputStream();FileOutputStream fos = new FileOutputStream(file);byte[] buffer = new byte[1024];int len = 0;while((len = is.read(buffer)) != -1){fos.write(buffer, 0, len);//设置进度条对话框进度count = count + len;pb.setProgress(count);}is.close();fos.close();}return file;}
4. 安装apk文件
/*** 安装apk文件流程* * a. 设置Action : Intent.ACTION_VIEW.* b. 设置数据和类型 : 设置apk文件的uri 和 MIME类型* c. 开启安装文件的Activity.*/protected void installApk() {Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");startActivity(intent);}
五. 相关的源码
(1) 布局文件
splash.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/ivt_splash" android:id="@+id/splash_rl"><ProgressBar android:id="@+id/pb"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_alignParentBottom="true"android:layout_marginBottom="30dip"/><TextView android:id="@+id/tv_version"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_above="@id/pb"android:layout_marginBottom="60dip"android:textSize="30sp"android:textColor="#17A6E8"android:text="version"/>
</RelativeLayout>
(2) Activity页面切换动画
main_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translateandroid:fromXDelta="100%p"android:toXDelta="0"android:fromYDelta="0"android:toYDelta="0" android:duration="200"/>
</set>
splash_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translateandroid:fromXDelta="0"android:toXDelta="-100%p"android:fromYDelta="0"android:toYDelta="0" android:duration="200"/>
</set>
(3) SplashActivity源码
SplashActivity.java
public class SplashActivity extends Activity {private static final String TAG = "SplashActivity";public static final int ERROR_GET_UPDATEINOF = 0;public static final int SUCESS_GET_UPDATEINOF = 1;public static final int ERROR_DOWNLOAD_APK = 2;public static final int SUCCESS_DOWNLOAD_APK = 3;private static final String XML_FILE_DIRECTORY = "updateinfo.xml";private static final String UPDATE_FOLDER_DIRECTORY = "/webupdate/";private TextView tv_version;private PackageManager pm;private String version;private UpdateInfo updateInfo;private Dialog mUpdateInfoDialog;private ProgressDialog mPb;private File apkFile;private RelativeLayout splash_rl;private long time;private SharedPreferences sp;private int touchPositionX0;private int touchPositionX1;private Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg) {switch (msg.what) {/** 获取更新信息错误 , 在断网或者获取信息出现异常执行* 提示一下, 之后进入主界面*/case ERROR_GET_UPDATEINOF:ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);loadMainUI();break;/** 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来* 如果版本号相同, 说明不用更新, 直接进入主界面* 如果版本号不同, 需要弹出更新对话框*/case SUCESS_GET_UPDATEINOF:if(updateInfo.getVersion().equals(version)){loadMainUI();}else{showUpdateDialog();}break;/** 下载apk文件出现错误, 中途断网 出现异常等情况* 提示后进入主界面*/case ERROR_DOWNLOAD_APK:mPb.dismiss();ToastHint.getInstance().showHint(R.string.fail_to_get_apk);loadMainUI();break;/** 成功下载apk文件之后执行的操作* 取消进度条对话框, 之后安装apk文件*/case SUCCESS_DOWNLOAD_APK:mPb.dismiss();installApk();break;default:break;}};};/*** 创建Activity时调用* * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题* ② 设置布局, 版本号, 执行动画 * ③ 设置当前时间* ④ 获取SharedPerference配置文件* ⑤ 开启检查版本号线程, 后续操作都在改线程中操作* */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//隐藏标题栏requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏状态栏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置布局setContentView(R.layout.splash);/** 显示当前软件的版本号* 获取布局中的TextView控件, 将版本号设置到这个TextView控件中*/tv_version = (TextView) findViewById(R.id.tv_version);version =getString(R.string.current_version) + " " + getVersion();tv_version.setText(version);/** 在界面设置一个动画, 用来表明正在运行* a. 获取布局* b. 创建一个动画对象* c. 将动画设置到布局中*/splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);alphaAnimation.setDuration(2000);splash_rl.setAnimation(alphaAnimation);/** 这个时间值是用来控制Splash界面显示时间的* 记录下这个值, 然后执行到下面, 如果时间差在3秒以内, * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差* 等够3秒在执行下面的操作*/time = System.currentTimeMillis();//从SharedPreference中获取一些配置sp = getSharedPreferences("config", Context.MODE_PRIVATE);//开启检查版本号线程new Thread(new CheckVersionTask()).start();}private final class CheckVersionTask implements Runnable{public void run() {try {/** 获取当前时间, 与onCreate方法中获取的时间进行比较* 如果不足3秒, 在等待够3秒之后在执行下面的操作*/long temp = System.currentTimeMillis();if(temp - time < 3000){SystemClock.sleep(temp - time);}/** 检查配置文件中的设置, 是否设置了自动更新; * 如果设置了自动更新, 就执行下面的操作,* 如果没有设置自动更新, 就直接进入主界面*/boolean is_auto_update = sp.getBoolean("is_auto_update", true);if(!is_auto_update){loadMainUI();return;}/** 获取更新信息* 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作* 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作* 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作*/updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);if(updateInfo != null){Message msg = new Message();msg.what = SUCESS_GET_UPDATEINOF;mHandler.sendMessage(msg);}else{Message msg = new Message();msg.what = ERROR_GET_UPDATEINOF;mHandler.sendMessage(msg);}} catch (Exception e) {e.printStackTrace();Message msg = new Message();msg.what = ERROR_GET_UPDATEINOF;mHandler.sendMessage(msg);}}}/*** 安装apk文件流程* * a. 设置Action : Intent.ACTION_VIEW.* b. 设置数据和类型 : 设置apk文件的uri 和 MIME类型* c. 开启安装文件的Activity.*/protected void installApk() {Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");startActivity(intent);}/*** 弹出更新对话框* * a. 创建builder对象* b. 设置标题* c. 设置对话框显示信息* d. 设置该对话框不可回退, 如果回退的话就会卡在本界面* e. 设置确定按钮* f. 设置取消按钮* g. 创建对话框* h. 显示对话框* * 确定按钮按下显示进度条对话框* a. 创建一个进度条对话框* b. 设置该对话框不能回退* c. 设置进度条样式* d. 设置进度条的信息* e. 显示进度条对话框* f. 开启一个线程, 下载apk文件*/protected void showUpdateDialog() {//创建builder对象AlertDialog.Builder builder = new AlertDialog.Builder(this);//设置标题builder.setTitle(getString(R.string.update_dialog_tittle));//设置对话框信息builder.setMessage(updateInfo.getDescription());//设置不可回退builder.setCancelable(false);//设置确定按钮builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {//创建进度条对话框mPb = new ProgressDialog(SplashActivity.this);//设置进度条对话框不可回退mPb.setCancelable(false);//设置进度条对话框样式mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//设置进度条对话框的信息mPb.setMessage(getString(R.string.update_dialog_messsage));//显示进度条对话框mPb.show();//开启显示进度条对话框线程new Thread(new DownloadApkTask()).start();}});builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {loadMainUI();}});//创建更新信息提示对话框mUpdateInfoDialog = builder.create();//显示更新信息提示对话框mUpdateInfoDialog.show();}/*** 在这个线程中主要执行downloadApk方法, 这个方法传入apk路径和进度条对话框* 注意 : 下载的前提是sd卡的状态是挂载的*/private final class DownloadApkTask implements Runnable{public void run() {if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){try {SystemClock.sleep(2000);apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);Message msg = new Message();msg.what = SUCCESS_DOWNLOAD_APK;mHandler.sendMessage(msg);} catch (Exception e) {e.printStackTrace();Message msg = new Message();msg.what = ERROR_DOWNLOAD_APK;mHandler.sendMessage(msg);}}}}/*** 下载apk更新文件* * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件* b. 创建URL对象* c. 创建HttpUrlConnection对象* d. 设置链接对象超时时间* e. 设置请求方式 get* f. 如果请求成功执行下面的操作* * g. 通过链接对象获取网络资源的大小* h. 将文件大小设置给进度条对话框* i. 获取输入流, 并且读取输入流信息* j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框*/public File downloadApk(String path,ProgressDialog pb) throws Exception{//创建本地文件对象File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));//创建HttpURL连接URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("GET");if(conn.getResponseCode() == 200){int max = conn.getContentLength();//设置进度条对话框的最大值pb.setMax(max);int count = 0;InputStream is = conn.getInputStream();FileOutputStream fos = new FileOutputStream(file);byte[] buffer = new byte[1024];int len = 0;while((len = is.read(buffer)) != -1){fos.write(buffer, 0, len);//设置进度条对话框进度count = count + len;pb.setProgress(count);}is.close();fos.close();}return file;}private String getFileName(String path){return path.substring(path.lastIndexOf("/") + 1);}private String getVersion() {try {pm = this.getPackageManager();PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);return packageInfo.versionName;} catch (Exception e) {e.printStackTrace();}return null;}/*** 获取更新信息* ① 根据字符串地址创建URL对象* ② 根据URL对象创建HttpURLConnection链接对象* ③ 设置链接对象5秒超时* ④ 设置链接对象获取的方式为get方式* ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流* ⑥ 解析输入流获取更新信息* */private UpdateInfo getUpdateInfo(String path){try {URL url = new URL(path); //创建URL对象//创建连接对象HttpURLConnection conn = (HttpURLConnection) url.openConnection();//设置链接超时conn.setConnectTimeout(5000);//设置获取方式conn.setRequestMethod("GET");//如果连接成功, 获取输入流if(conn.getResponseCode() == 200){InputStream is = conn.getInputStream();//解析输入流中的数据, 返回更新信息return parserUpdateInfo(is);}} catch (MalformedURLException e) {e.printStackTrace();} catch (ProtocolException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}/*** 获取更新信息* ① 创建pull解析器* ② 为解析器设置编码格式* ③ 获取解析事件* ④ 遍历整个xml文件节点, 获取标签元素内容*/private UpdateInfo parserUpdateInfo(InputStream is){try {UpdateInfo updateInfo = null;//1. 创建pull解析解析器XmlPullParser parser = Xml.newPullParser();//2. 设置解析编码parser.setInput(is, "UTF-8");//3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等int eventType = parser.getEventType();//4. 在文档结束前一直解析while (eventType != XmlPullParser.END_DOCUMENT) {switch (eventType) {//只解析标签case XmlPullParser.START_TAG:if ("updateInfo".equals(parser.getName())) {//当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象updateInfo = new UpdateInfo();} else if ("version".equals(parser.getName())) {//解析版本号标签updateInfo.setVersion(parser.nextText());} else if ("url".equals(parser.getName())) {//解析url标签updateInfo.setUrl(parser.nextText());} else if ("description".equals(parser.getName())) {//解析描述标签updateInfo.setDescription(parser.nextText());}break;default:break;}//每解析完一个元素, 就将解析标志位下移eventType = parser.next();}is.close();return updateInfo;} catch (XmlPullParserException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}private void loadMainUI(){Intent intent = new Intent(this,HomeActivity.class);startActivity(intent);finish();overridePendingTransition(R.anim.main_in, R.anim.splash_out);}public class UpdateInfo {private String version; //当前软件版本号private String url; //获取到的软件地址private String description; //软件描述public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}@Overridepublic String toString() {return "UpdateInfo [version=" + version + ", url=" + url+ ", description=" + description + "]";}}/*** 设置触摸事件* 在手指按下时记录x坐标值 , 在手指抬起的时候记录x坐标值 , 如果两个值相差超过100* 那么跳转到主界面 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)*/@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN :touchPositionX0 = (int) event.getX();break;case MotionEvent.ACTION_UP :touchPositionX1 = (int) event.getX();if((touchPositionX0 - touchPositionX1) > 100)loadMainUI();touchPositionX0 = 0;touchPositionX1 = 0;break;}return true;}
}
.
作者 :万境绝尘
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835
.
【Android 应用开发】Android应用的自动更新模块相关推荐
- android程序突然崩溃如何调试,Android应用开发android 调试崩溃Unable to instantiate application的解决方法...
本文将带你了解Android应用开发android 调试崩溃Unable to instantiate application的解决方法,希望本文对大家学Android有所帮助. 更新后Android ...
- android studio真机测试不生成apk,Android应用开发Android Studio编译出来的APK无法安装如何解决?...
本文将带你了解Android应用开发Android Studio编译出来的APK无法安装如何解决?,希望本文对大家学Android有所帮助. Android Studio编译出来的APK无法安装如何解 ...
- android ble 设备扫描程序,Android应用开发Android 7.0 BLE scan 问题:程序无错但扫描不到BLE设备...
本文将带你了解Android应用开发Android 7.0 BLE scan 问题:程序无错但扫描不到BLE设备,希望本文对大家学Android有所帮助. < 最近在做毕设,需要几周内从头学起 ...
- android studio socket 失败,Android应用开发Android Studio建立Socket连接失败解决方法
本文将带你了解Android应用开发Android Studio建立Socket连接失败解决方法,希望本文对大家学Android有所帮助. < Android Studio建立Socket连接失 ...
- android落下动画,Android应用开发android 购物车小球掉落动画
本文将带你了解Android应用开发android 购物车小球掉落动画,希望本文对大家学Android有所帮助. 先贴效果图 对自定义View小红球的绘制 public class BallView ...
- android baseactivity,Android应用开发Android通过BaseActivity获取到当前启动的Activity名称...
本文将带你了解Android应用开发Android通过BaseActivity获取到当前启动的Activity名称,希望本文对大家学Android有所帮助. < 在BaseActivity的on ...
- android view 点击变暗,Android应用开发Android ImageView点击变暗效果
本文将带你了解Android应用开发Android ImageView点击变暗效果,希望本文对大家学Android有所帮助. < 自定义ImageView: 在ImageView中setPres ...
- android 防止重复启动app,Android应用开发Android 防止启动页面(SplashActivity)被多次启动...
本文将带你了解Android应用开发Android 防止启动页面(SplashActivity)被多次启动,希望本文对大家学Android有所帮助. 话不多说直接上代码: @Override prot ...
- android studio获取数字签名,Android应用开发Android Studio数字签名打包apk图文步骤教程...
本文将带你了解Android应用开发Android Studio数字签名打包apk图文步骤教程,希望本文对大家学Android有所帮助. Android Studio数字签名打包release版apk ...
- android修改用户名和密码错误,Android应用开发Android Studio 修改用户名、密码、URL等操作教程...
本文将带你了解Android应用开发Android Studio 修改用户名.密码.URL等操作教程,希望本文对大家学Android有所帮助. 修改用户名.密码: 在AndroidStudio的ter ...
最新文章
- java 排序算法 讲解_java实现排序算法之冒泡排序法详细讲解
- SPI的原理以及Verilog HDL实现
- jQuery中find()方法和filter()方法的区别
- 利用PyMySQL模块操作数据库
- VHDL移位寄存器的设计与实现
- React开发(172):React引入背景图片
- NOI2011 智能车比赛
- Matlab面向对象编程
- 通过wifi使用nfs把ubuntu挂载到android
- 机器人潘森护盾_新版潘森无伤抗塔 E技能护盾机制全解析
- 新手学编程?选python吧!
- XShell下载安装并连接阿里云
- c语言md5函数 linux,【转】MD5校验C语言实现源代码
- 解决matlab错误使用 mex 未找到支持的编译器或 SDK一系列问题
- java用swing日历标记节日,java基于swing实现日历
- linux grep 和 find 命令
- 《关爱码农成长计划》第一期报告
- 无情链表的创建,,插入,,删除第一个位
- Windows游戏编程之从零开始d
- 合作伙伴眼中的鸿蒙,专访海雀科技研发总监李尹
热门文章
- 简单的ALV显示例子
- 乱码问题引申 python 中string和unicode
- 总结sqlserver数据库性能优化相关的注意事项
- (转)软件开发人员如何提高自己的软件专业技术方面的具体建议
- 趣谈Linux操作系统学习笔记:用户态内存映射:如何找到正确的会议室?(第25讲)...
- Android点击事件(click button)的四种写法
- NSS_06 extjs弹出窗口上的文本框默认获得焦点
- Linux下时间戳格式和标准时间格式的转换
- 也谈Hashtable
- 从oracle10g 10.2.0.1 升级到10.2.0.4碰到的问题及解决