软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很流行使用Splash界面, 正好与自动更新配套在一起;

 

在这个自动更新Splash中, 使用到了 动画设置 ,SharedPerference ,pull解析 ,dialog对话框 ,http网络编程,handler 等.

 

注意一个错误 : 已安装具有该名称和不同签名的数据包 , 早上测试人员报告突然出现这个问题, 在开发的时候我直接将eclipse上编译的版本放到了服务器上, 最后出现了这个问题, 开发的时候明明是好的啊, 怎么测试的时候出问题了呢.

编译环境不同, 产生的签名是不一样的, 在eclipse上编译生成 与 正式版本在linux下编译 所产生的 数字签名 是不一样的.

 

一. 创建Activity

 

1. 创建Activity大概流程

a. 设置全屏显示.

b. 设置布局, 并在布局中显示当前版本号, 为Splash界面添加动画.

c. 获取当前时间.

d. 获取SharedPerence配置文件.

e. 开启检查版本号线程, 后续的操作都在这个线程中执行.

 

2. 设置窗口样式

 

(1) 设置全屏显示

a. 代码实现 : 由于是Splash界面, 这里需要设置成无标题, 并且全屏显示, 注意下面的两行代码需要在setContentView()方法之前调用;

 

1
2
3
4
5
//隐藏标题栏
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) 关于窗口的其它设置

 

1
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. 配置文件实现

 

1
2
3
4
5
/设置横屏
<activity android:name="myAcitivty"  android:screenOrientation="landscape" />    
 
//设置竖屏
<activity android:name="myAcitivty"  android:screenOrientation="portrait" />

 

 

b. 代码实现

 

1
2
3
4
5
//设置横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
 
//设置竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

 

 

c. 获取屏幕方向

 

1
2
//获取横屏方向
int orientation = this.getResources().getConfiguration().orientation;

其中的orientation方向可以使 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 或者 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE .

 

3. 设置动画

 

为了更好的用户体验, 这里给Splash界面添加一个动画, 这个动画加给整个界面.

 

(1) 创建动画

 

 

1
2
3
AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);<span style="white-space:pre;">   </span>//创建动画
animation.setDuration(2000);<span style="white-space:pre;"> </span>//设置渐变
splash_rl.setAnimation(animation);<span style="white-space:pre;">   </span>//设置动画载体

创建动画吧: 创建的这个动画是透明度渐变动画, 传入浮点型参数, 0代表完全透明, 1代表不透明, 传入参数代表透明度从完全透明到不透明.

 

设置时间 : 设置的duration是动画渐变过程所消耗的时间.

设置动画 : 最后使用setAnimation()方法将穿件的动画设置给Splash界面.

 

(2) 动画常用方法

 

a. 普通设置 

 

 

        alphaAnimation.setRepeatCount(5);//设置重复次数
        alphaAnimation.setFillAfter(true);//动画执行完是否停留在执行完的状态
        alphaAnimation.setStartOffset(1000);//动画执行前等待的时间, 单位是毫秒
        alphaAnimation.start();//开始动画

 

 


b. 设置监听器

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
alphaAnimation.setAnimationListener(new AnimationListener() {
            //动画开始时回调
            @Override
            public void onAnimationStart(Animation animation) {
            }
            //动画重复执行时回调
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
            //动画执行结束时回调
            @Override
            public void onAnimationEnd(Animation animation) {
            }
        });

 

 

4. SharedPerference使用

 

 

1
2
3
4
5
6
7
8
//获取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()方法代码 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
     * 创建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. 获取信息 : 从网络中获取更新信息, 根据是否成功获取信息执行不同的操作.

 

源码 : 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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. 解析输入流信息;

 

源码 : 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * 获取更新信息
 *      ① 根据字符串地址创建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文件地址, 软件描述等信息封装在一个类中.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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文件

 

 

1
2
3
4
5
6
<?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) 源码

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
 * 获取更新信息
 *      ① 创建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对象用来控制整个更新过程的进行;

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
        }
    };
};

 

 

四. 下载安装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) 源码 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * 弹出更新对话框
 *
 * 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线程

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * 在这个线程中主要执行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);

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * 下载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文件

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * 安装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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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

 

1
2
3
4
5
6
7
8
9
10
11
<?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

 

1
2
3
4
5
6
7
8
9
10
11
<?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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
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 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 + "]";
        }
    }
}

 

来自:http://blog.csdn.net/shulianghan/article/details/16879203

arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()