zoukankan      html  css  js  c++  java
  • 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)
    	 */
    	@Override
    	public 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() {
            	//动画开始时回调
    			@Override
    			public void onAnimationStart(Animation animation) {
    			}
    			//动画重复执行时回调
    			@Override
    			public void onAnimationRepeat(Animation animation) {
    			}
    			//动画执行结束时回调
    			@Override
    			public void onAnimationEnd(Animation animation) {
    			}
    		});


    4. SharedPerference使用


            //获取SharedPerference
            SharedPreferences 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配置文件
    	 * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作
    	 * 
    	 */
    	@Override
        public 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;
    		}
    		@Override
    		public 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"
         >
        <translate
            android: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"
         >
        <translate
            android: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配置文件
    	 * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作
    	 * 
    	 */
    	@Override
        public 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;
    		}
    		@Override
    		public String toString() {
    			return "UpdateInfo [version=" + version + ", url=" + url
    					+ ", description=" + description + "]";
    		}
    	}
    	
    	/**
    	 *	设置触摸事件
    	 *	在手指按下时记录x坐标值 , 在手指抬起的时候记录x坐标值 , 如果两个值相差超过100
    	 *	那么跳转到主界面 
    	 * @see android.app.Activity#onTouchEvent(android.view.MotionEvent)
    	 */
    	@Override
    	public 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

    .



  • 相关阅读:
    本周学习进度条11
    编程珠玑阅读笔记01
    单词统计
    本周学习进度条10
    《构建之法》读后感三
    第九周总结
    第一阶段冲刺
    第八周总结
    wpf图片浏览器,实现缩放平移操作图片切换等功能
    获取当前进程(程序)主窗体句柄并设置wpf的父窗体为此句柄
  • 原文地址:https://www.cnblogs.com/hanshuliang/p/4215460.html
Copyright © 2011-2022 走看看