【Android 应用开发】Android应用的自动更新模块(三)

四. 下载安装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;
    }
}
上一篇:【云计算 Hadoop】Hadoop 版本 生态圈 MapReduce模型(二)


下一篇:【嵌入式开发】C语言 指针数组 多维数组(一)