zoukankan      html  css  js  c++  java
  • Android应用的自动更新模块

    软件的自动更新一般都与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. requestWindowFeature(Window.FEATURE_NO_TITLE);  
    3. //隐藏状态栏  
    4. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,   
    5.         WindowManager.LayoutParams.FLAG_FULLSCREEN);  

    b. 配置实现 : 

      

    1. AndroidManifest.xml  
    2. <activity   
    3.     android:name="myAcitivty"    
    4.     android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />  

     

    (2) 关于窗口的其它设置

      

    1. //①设置窗体始终点亮  
    2. getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  

      

    1. //②设置窗体始终点亮  
    2. getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  

     

    设置窗体始终点亮的配置文件实现

      

    1. //③AndroidManifest.xml添加权限  
    2. <uses-permission android:name="android.permission.WAKE_LOCK" />  



     

    1. //设置窗体背景模糊  
    2. getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_BLUR_BEHIND);  

    (3) 屏幕方向设置

     

    a. 配置文件实现 

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

     

    b. 代码实现 

    1. //设置横屏  
    2. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);  
    3.   
    4. //设置竖屏  
    5. 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. AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);<span style="white-space:pre">    </span>//创建动画  
    2. animation.setDuration(2000);<span style="white-space:pre">  </span>//设置渐变  
    3. splash_rl.setAnimation(animation);<span style="white-space:pre">    </span>//设置动画载体  

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

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

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

     

    (2) 动画常用方法

    a. 普通设置  

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


    b. 设置监听器

     

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

    4. SharedPerference使用 

    1. //获取SharedPerference  
    2. SharedPreferences sharedPreferences = getSharedPreferences("sp", Context.MODE_PRIVATE);  
    3.   
    4. Editor editor = sharedPreferences.edit();   //获取Editor对象  
    5. editor.putBoolean("isUpdate"true);        //向sp中写入数据  
    6. editor.commit();                            //提交  
    7.       
    8. sharedPreferences.getBoolean("isUpdate"true);//获取sp中的变量  



    5. onCreate()方法代码  

    1. /** 
    2.      * 创建Activity时调用 
    3.      *  
    4.      * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题 
    5.      * ② 设置布局, 版本号, 执行动画  
    6.      * ③ 设置当前时间 
    7.      * ④ 获取SharedPerference配置文件 
    8.      * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作 
    9.      *  
    10.      */  
    11.     @Override  
    12.     public void onCreate(Bundle savedInstanceState) {  
    13.         super.onCreate(savedInstanceState);  
    14.         //隐藏标题栏  
    15.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
    16.         //隐藏状态栏  
    17.         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,   
    18.                 WindowManager.LayoutParams.FLAG_FULLSCREEN);  
    19.         //设置布局  
    20.         setContentView(R.layout.splash);  
    21.           
    22.         /* 
    23.          *  显示当前软件的版本号 
    24.          *  获取布局中的TextView控件, 将版本号设置到这个TextView控件中 
    25.          */  
    26.         tv_version = (TextView) findViewById(R.id.tv_version);  
    27.         version =getString(R.string.current_version) + " " + getVersion();  
    28.         tv_version.setText(version);  
    29.           
    30.         /* 
    31.          *  在界面设置一个动画, 用来表明正在运行 
    32.          *  a. 获取布局 
    33.          *  b. 创建一个动画对象 
    34.          *  c. 将动画设置到布局中 
    35.          */  
    36.         splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);  
    37.         AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);  
    38.         animation.setDuration(2000);  
    39.         splash_rl.setAnimation(animation);  
    40.           
    41.         /* 
    42.          * 这个时间值是用来控制Splash界面显示时间的 
    43.          * 记录下这个值, 然后执行到下面, 如果时间差在3秒以内,  
    44.          * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差 
    45.          * 等够3秒在执行下面的操作 
    46.          */  
    47.         time = System.currentTimeMillis();  
    48.   
    49.         //从SharedPreference中获取一些配置  
    50.         sp = getSharedPreferences("config", Context.MODE_PRIVATE);  
    51.           
    52.         //开启检查版本号线程  
    53.         new Thread(new CheckVersionTask()).start();  
    54.     }  



     

    二. 检查版本号

    1. 检查版本号线程

    流程 : 

    a. 保持Splash持续时间 : 获取当前时间与time进行比较, 如果不足3秒, 人为使Splash保持3秒时间;

    b. 查看更新设置 : 从sp中获取更新设置, 如果sp中自动更新为true, 那么就执行下面的更新流程, 如果sp中自动更新为false, 那么直接进入主界面.

    c. 获取信息 : 从网络中获取更新信息, 根据是否成功获取信息执行不同的操作.

     

    源码 :  

    1. private final class CheckVersionTask implements Runnable{  
    2.     public void run() {  
    3.         try {  
    4.             /* 
    5.              * 获取当前时间, 与onCreate方法中获取的时间进行比较 
    6.              * 如果不足3秒, 在等待够3秒之后在执行下面的操作 
    7.              */  
    8.             long temp = System.currentTimeMillis();  
    9.             if(temp - time < 3000){  
    10.                 SystemClock.sleep(temp - time);  
    11.             }  
    12.               
    13.             /* 
    14.              * 检查配置文件中的设置, 是否设置了自动更新;  
    15.              * 如果设置了自动更新, 就执行下面的操作, 
    16.              * 如果没有设置自动更新, 就直接进入主界面 
    17.              */  
    18.             boolean is_auto_update = sp.getBoolean("is_auto_update"true);  
    19.             if(!is_auto_update){  
    20.                 loadMainUI();  
    21.                 return;  
    22.             }  
    23.               
    24.             /* 
    25.              * 获取更新信息 
    26.              * 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作 
    27.              * 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作 
    28.              * 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作 
    29.              */  
    30.             updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);  
    31.             if(updateInfo != null){  
    32.                 Message msg = new Message();  
    33.                 msg.what = SUCESS_GET_UPDATEINOF;  
    34.                 mHandler.sendMessage(msg);  
    35.             }else{  
    36.                 Message msg = new Message();  
    37.                 msg.what = ERROR_GET_UPDATEINOF;  
    38.                 mHandler.sendMessage(msg);  
    39.             }  
    40.         } catch (Exception e) {  
    41.             e.printStackTrace();  
    42.             Message msg = new Message();  
    43.             msg.what = ERROR_GET_UPDATEINOF;  
    44.             mHandler.sendMessage(msg);  
    45.         }  
    46.     }  
    47.    }  



    2. 获取版本号方法

    流程 : 

    a. 创URL建对象;

    b. 创建HttpURLConnection对象;

    c. 设置超时时间;

    d. 设置获取方式;

    e. 查看链接是否成功;

    f. 解析输入流信息;

     

    源码 :  

    1. /** 
    2.  * 获取更新信息 
    3.  *      ① 根据字符串地址创建URL对象 
    4.  *      ② 根据URL对象创建HttpURLConnection链接对象 
    5.  *      ③ 设置链接对象5秒超时 
    6.  *      ④ 设置链接对象获取的方式为get方式 
    7.  *      ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流 
    8.  *      ⑥ 解析输入流获取更新信息 
    9.  *       
    10.  */  
    11. private UpdateInfo getUpdateInfo(String path){  
    12.     try {  
    13.         URL url = new URL(path);    //创建URL对象  
    14.         //创建连接对象  
    15.         HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
    16.         //设置链接超时  
    17.         conn.setConnectTimeout(5000);  
    18.         //设置获取方式  
    19.         conn.setRequestMethod("GET");  
    20.         //如果连接成功, 获取输入流  
    21.         if(conn.getResponseCode() == 200){  
    22.             InputStream is = conn.getInputStream();  
    23.             //解析输入流中的数据, 返回更新信息  
    24.             return parserUpdateInfo(is);  
    25.         }  
    26.     } catch (MalformedURLException e) {  
    27.         e.printStackTrace();  
    28.     } catch (ProtocolException e) {  
    29.         e.printStackTrace();  
    30.     } catch (IOException e) {  
    31.         e.printStackTrace();  
    32.     }  
    33.     return null;  
    34. }  

    3. 更新信息对象

     

    将从网上获取的更新信息 包括 版本号, apk文件地址, 软件描述等信息封装在一个类中. 

    1. public class UpdateInfo {  
    2.     private String version; //当前软件版本号  
    3.     private String url;     //获取到的软件地址  
    4.     private String description; //软件描述  
    5.       
    6.     public String getVersion() {  
    7.         return version;  
    8.     }  
    9.     public void setVersion(String version) {  
    10.         this.version = version;  
    11.     }  
    12.     public String getUrl() {  
    13.         return url;  
    14.     }  
    15.     public void setUrl(String url) {  
    16.         this.url = url;  
    17.     }  
    18.     public String getDescription() {  
    19.         return description;  
    20.     }  
    21.     public void setDescription(String description) {  
    22.         this.description = description;  
    23.     }  
    24.     @Override  
    25.     public String toString() {  
    26.         return "UpdateInfo [version=" + version + ", url=" + url  
    27.                 + ", description=" + description + "]";  
    28.     }  
    29. }  

    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. <?xml version="1.0" encoding="UTF-8"?>  
    2. <updateInfo>  
    3.   <version>3.2</version>  
    4.   <url>http://127.0.0.1:8080/web/mobilesafe.apk</url>  
    5.   <description>客户端更新</description>  
    6. </updateInfo>  

    (3) 源码 

    1. /** 
    2.  * 获取更新信息 
    3.  *      ① 创建pull解析器 
    4.  *      ② 为解析器设置编码格式 
    5.  *      ③ 获取解析事件 
    6.  *      ④ 遍历整个xml文件节点, 获取标签元素内容 
    7.  */  
    8. private UpdateInfo parserUpdateInfo(InputStream is){  
    9.     try {  
    10.         UpdateInfo updateInfo = null;  
    11.         //1. 创建pull解析解析器  
    12.         XmlPullParser parser = Xml.newPullParser();  
    13.         //2. 设置解析编码  
    14.         parser.setInput(is, "UTF-8");  
    15.         //3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等  
    16.         int eventType = parser.getEventType();  
    17.         //4. 在文档结束前一直解析  
    18.         while (eventType != XmlPullParser.END_DOCUMENT) {  
    19.             switch (eventType) {  
    20.             //只解析标签  
    21.             case XmlPullParser.START_TAG:  
    22.                 if ("updateInfo".equals(parser.getName())) {  
    23.                     //当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象  
    24.                     updateInfo = new UpdateInfo();  
    25.                 } else if ("version".equals(parser.getName())) {  
    26.                     //解析版本号标签  
    27.                     updateInfo.setVersion(parser.nextText());  
    28.                 } else if ("url".equals(parser.getName())) {  
    29.                     //解析url标签  
    30.                     updateInfo.setUrl(parser.nextText());  
    31.                 } else if ("description".equals(parser.getName())) {  
    32.                     //解析描述标签  
    33.                     updateInfo.setDescription(parser.nextText());  
    34.                 }  
    35.                 break;  
    36.             default:  
    37.                 break;  
    38.             }  
    39.             //每解析完一个元素, 就将解析标志位下移  
    40.             eventType = parser.next();  
    41.         }  
    42.         is.close();  
    43.         return updateInfo;  
    44.     } catch (XmlPullParserException e) {  
    45.         e.printStackTrace();  
    46.     } catch (IOException e) {  
    47.         e.printStackTrace();  
    48.     }  
    49.     return null;  
    50. }  

    三. Handler对象

     

    Handler对象用来控制整个更新过程的进行; 

    1. private Handler mHandler = new Handler(){  
    2.     public void handleMessage(android.os.Message msg) {  
    3.         switch (msg.what) {  
    4.         /* 
    5.          * 获取更新信息错误 , 在断网或者获取信息出现异常执行 
    6.          * 提示一下, 之后进入主界面 
    7.          */  
    8.         case ERROR_GET_UPDATEINOF:  
    9.             ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);  
    10.             loadMainUI();  
    11.             break;  
    12.         /* 
    13.          * 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来 
    14.          * 如果版本号相同, 说明不用更新, 直接进入主界面 
    15.          * 如果版本号不同, 需要弹出更新对话框 
    16.          */  
    17.         case SUCESS_GET_UPDATEINOF:  
    18.             if(updateInfo.getVersion().equals(version)){  
    19.                 loadMainUI();  
    20.             }else{  
    21.                 showUpdateDialog();  
    22.             }  
    23.             break;  
    24.         /* 
    25.          * 下载apk文件出现错误, 中途断网 出现异常等情况 
    26.          * 提示后进入主界面 
    27.          */  
    28.         case ERROR_DOWNLOAD_APK:  
    29.             mPb.dismiss();  
    30.             ToastHint.getInstance().showHint(R.string.fail_to_get_apk);  
    31.             loadMainUI();  
    32.             break;  
    33.         /* 
    34.          * 成功下载apk文件之后执行的操作 
    35.          * 取消进度条对话框, 之后安装apk文件 
    36.          */  
    37.         case SUCCESS_DOWNLOAD_APK:  
    38.             mPb.dismiss();  
    39.             installApk();  
    40.             break;  
    41.         default:  
    42.             break;  
    43.         }  
    44.     };  
    45. };  

    四. 下载安装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.  * a. 创建builder对象 
    5.  * b. 设置标题 
    6.  * c. 设置对话框显示信息 
    7.  * d. 设置该对话框不可回退, 如果回退的话就会卡在本界面 
    8.  * e. 设置确定按钮 
    9.  * f. 设置取消按钮 
    10.  * g. 创建对话框 
    11.  * h. 显示对话框 
    12.  *  
    13.  * 确定按钮按下显示进度条对话框 
    14.  * a. 创建一个进度条对话框 
    15.  * b. 设置该对话框不能回退 
    16.  * c. 设置进度条样式 
    17.  * d. 设置进度条的信息 
    18.  * e. 显示进度条对话框 
    19.  * f. 开启一个线程, 下载apk文件 
    20.  */  
    21. protected void showUpdateDialog() {  
    22.     //创建builder对象  
    23.     AlertDialog.Builder builder = new AlertDialog.Builder(this);  
    24.     //设置标题  
    25.     builder.setTitle(getString(R.string.update_dialog_tittle));  
    26.     //设置对话框信息  
    27.     builder.setMessage(updateInfo.getDescription());  
    28.     //设置不可回退  
    29.     builder.setCancelable(false);  
    30.     //设置确定按钮  
    31.     builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {  
    32.         public void onClick(DialogInterface dialog, int which) {  
    33.             //创建进度条对话框  
    34.             mPb = new ProgressDialog(SplashActivity.this);  
    35.             //设置进度条对话框不可回退  
    36.             mPb.setCancelable(false);  
    37.             //设置进度条对话框样式  
    38.             mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);  
    39.             //设置进度条对话框的信息  
    40.             mPb.setMessage(getString(R.string.update_dialog_messsage));  
    41.             //显示进度条对话框  
    42.             mPb.show();  
    43.             //开启显示进度条对话框线程  
    44.             new Thread(new DownloadApkTask()).start();  
    45.         }  
    46.     });  
    47.     builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {  
    48.         public void onClick(DialogInterface dialog, int which) {  
    49.             loadMainUI();  
    50.         }  
    51.     });  
    52.     //创建更新信息提示对话框  
    53.     mUpdateInfoDialog = builder.create();  
    54.     //显示更新信息提示对话框  
    55.     mUpdateInfoDialog.show();  
    56. }  

    2. 下载apk线程 

    1. /** 
    2.  * 在这个线程中主要执行downloadApk方法, 这个方法传入apk路径和进度条对话框 
    3.  * 注意 : 下载的前提是sd卡的状态是挂载的 
    4.  */  
    5. private final class DownloadApkTask implements Runnable{  
    6.     public void run() {  
    7.         if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){  
    8.             try {  
    9.                 SystemClock.sleep(2000);  
    10.                 apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);  
    11.                 Message msg = new Message();  
    12.                 msg.what = SUCCESS_DOWNLOAD_APK;  
    13.                 mHandler.sendMessage(msg);  
    14.             } catch (Exception e) {  
    15.                 e.printStackTrace();  
    16.                 Message msg = new Message();  
    17.                 msg.what = ERROR_DOWNLOAD_APK;  
    18.                 mHandler.sendMessage(msg);  
    19.             }  
    20.         }  
    21.     }  
    22.    }  

    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.  * 下载apk更新文件 
    3.  *   
    4.  * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件 
    5.  * b. 创建URL对象 
    6.  * c. 创建HttpUrlConnection对象 
    7.  * d. 设置链接对象超时时间 
    8.  * e. 设置请求方式 get 
    9.  * f. 如果请求成功执行下面的操作 
    10.  *  
    11.  * g. 通过链接对象获取网络资源的大小 
    12.  * h. 将文件大小设置给进度条对话框 
    13.  * i. 获取输入流, 并且读取输入流信息 
    14.  * j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框 
    15.  */  
    16. public File downloadApk(String path,ProgressDialog pb) throws Exception{  
    17.     //创建本地文件对象  
    18.     File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));  
    19.     //创建HttpURL连接  
    20.     URL url = new URL(path);  
    21.     HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
    22.     conn.setConnectTimeout(5000);  
    23.     conn.setRequestMethod("GET");  
    24.     if(conn.getResponseCode() == 200){  
    25.         int max = conn.getContentLength();  
    26.         //设置进度条对话框的最大值  
    27.         pb.setMax(max);  
    28.         int count = 0;  
    29.         InputStream is = conn.getInputStream();  
    30.         FileOutputStream fos = new FileOutputStream(file);  
    31.         byte[] buffer = new byte[1024];  
    32.         int len = 0;  
    33.         while((len = is.read(buffer)) != -1){  
    34.             fos.write(buffer, 0, len);  
    35.             //设置进度条对话框进度  
    36.             count = count + len;  
    37.             pb.setProgress(count);  
    38.         }  
    39.         is.close();  
    40.         fos.close();  
    41.     }  
    42.     return file;  
    43. }  

    4. 安装apk文件 

    1. /** 
    2.  * 安装apk文件流程 
    3.  *  
    4.  * a. 设置Action : Intent.ACTION_VIEW. 
    5.  * b. 设置数据和类型 : 设置apk文件的uri 和 MIME类型 
    6.  * c. 开启安装文件的Activity. 
    7.  */  
    8. protected void installApk() {  
    9.     Intent intent = new Intent();  
    10.     intent.setAction(Intent.ACTION_VIEW);  
    11.     intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");  
    12.     startActivity(intent);  
    13. }  

    五. 相关的源码 

     

    (1) 布局文件

    splash.xml 

    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:layout_width="match_parent"  
    4.     android:layout_height="match_parent"  
    5.     android:background="@drawable/ivt_splash"   
    6.     android:id="@+id/splash_rl">  
    7.   
    8.     <ProgressBar android:id="@+id/pb"  
    9.         android:layout_width="wrap_content"  
    10.         android:layout_height="wrap_content"  
    11.         android:layout_centerHorizontal="true"  
    12.         android:layout_alignParentBottom="true"  
    13.         android:layout_marginBottom="30dip"/>  
    14.       
    15.     <TextView android:id="@+id/tv_version"  
    16.        android:layout_width="wrap_content"  
    17.         android:layout_height="wrap_content"  
    18.         android:layout_centerHorizontal="true"  
    19.         android:layout_above="@id/pb"  
    20.         android:layout_marginBottom="60dip"  
    21.         android:textSize="30sp"  
    22.         android:textColor="#17A6E8"  
    23.         android:text="version"  
    24.         />  
    25. </RelativeLayout>  



     

    (2) Activity页面切换动画

     

    main_in.xml 

    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <set xmlns:android="http://schemas.android.com/apk/res/android"  
    3.      >  
    4.     <translate  
    5.         android:fromXDelta="100%p"  
    6.         android:toXDelta="0"  
    7.         android:fromYDelta="0"  
    8.         android:toYDelta="0"   
    9.         android:duration="200"  
    10.         />  
    11. </set>  

     

    splash_out.xml 

    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <set xmlns:android="http://schemas.android.com/apk/res/android"  
    3.      >  
    4.     <translate  
    5.         android:fromXDelta="0"  
    6.         android:toXDelta="-100%p"  
    7.         android:fromYDelta="0"  
    8.         android:toYDelta="0"   
    9.         android:duration="200"  
    10.         />  
    11. </set>  

     

    (3) SplashActivity源码

     

    SplashActivity.java 

      1. public class SplashActivity extends Activity {  
      2.   
      3.     private static final String TAG = "SplashActivity";  
      4.       
      5.     public static final int ERROR_GET_UPDATEINOF = 0;  
      6.     public static final int SUCESS_GET_UPDATEINOF = 1;  
      7.     public static final int ERROR_DOWNLOAD_APK = 2;  
      8.     public static final int SUCCESS_DOWNLOAD_APK = 3;  
      9.       
      10.     private static final String XML_FILE_DIRECTORY = "updateinfo.xml";  
      11.     private static final String UPDATE_FOLDER_DIRECTORY = "/webupdate/";  
      12.       
      13.     private TextView tv_version;  
      14.     private PackageManager pm;  
      15.     private String version;  
      16.     private UpdateInfo updateInfo;  
      17.       
      18.     private Dialog mUpdateInfoDialog;  
      19.     private ProgressDialog mPb;  
      20.     private File apkFile;  
      21.       
      22.     private RelativeLayout splash_rl;  
      23.     private long time;  
      24.     private SharedPreferences sp;  
      25.       
      26.     private Handler mHandler = new Handler(){  
      27.         public void handleMessage(android.os.Message msg) {  
      28.             switch (msg.what) {  
      29.             /* 
      30.              * 获取更新信息错误 , 在断网或者获取信息出现异常执行 
      31.              * 提示一下, 之后进入主界面 
      32.              */  
      33.             case ERROR_GET_UPDATEINOF:  
      34.                 ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);  
      35.                 loadMainUI();  
      36.                 break;  
      37.             /* 
      38.              * 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来 
      39.              * 如果版本号相同, 说明不用更新, 直接进入主界面 
      40.              * 如果版本号不同, 需要弹出更新对话框 
      41.              */  
      42.             case SUCESS_GET_UPDATEINOF:  
      43.                 if(updateInfo.getVersion().equals(version)){  
      44.                     loadMainUI();  
      45.                 }else{  
      46.                     showUpdateDialog();  
      47.                 }  
      48.                 break;  
      49.             /* 
      50.              * 下载apk文件出现错误, 中途断网 出现异常等情况 
      51.              * 提示后进入主界面 
      52.              */  
      53.             case ERROR_DOWNLOAD_APK:  
      54.                 mPb.dismiss();  
      55.                 ToastHint.getInstance().showHint(R.string.fail_to_get_apk);  
      56.                 loadMainUI();  
      57.                 break;  
      58.             /* 
      59.              * 成功下载apk文件之后执行的操作 
      60.              * 取消进度条对话框, 之后安装apk文件 
      61.              */  
      62.             case SUCCESS_DOWNLOAD_APK:  
      63.                 mPb.dismiss();  
      64.                 installApk();  
      65.                 break;  
      66.             default:  
      67.                 break;  
      68.             }  
      69.         };  
      70.     };  
      71.       
      72.     /** 
      73.      * 创建Activity时调用 
      74.      *  
      75.      * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题 
      76.      * ② 设置布局, 版本号, 执行动画  
      77.      * ③ 设置当前时间 
      78.      * ④ 获取SharedPerference配置文件 
      79.      * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作 
      80.      *  
      81.      */  
      82.     @Override  
      83.     public void onCreate(Bundle savedInstanceState) {  
      84.         super.onCreate(savedInstanceState);  
      85.         //隐藏标题栏  
      86.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
      87.         //隐藏状态栏  
      88.         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,   
      89.                 WindowManager.LayoutParams.FLAG_FULLSCREEN);  
      90.         //设置布局  
      91.         setContentView(R.layout.splash);  
      92.           
      93.         /* 
      94.          *  显示当前软件的版本号 
      95.          *  获取布局中的TextView控件, 将版本号设置到这个TextView控件中 
      96.          */  
      97.         tv_version = (TextView) findViewById(R.id.tv_version);  
      98.         version =getString(R.string.current_version) + " " + getVersion();  
      99.         tv_version.setText(version);  
      100.           
      101.         /* 
      102.          *  在界面设置一个动画, 用来表明正在运行 
      103.          *  a. 获取布局 
      104.          *  b. 创建一个动画对象 
      105.          *  c. 将动画设置到布局中 
      106.          */  
      107.         splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);  
      108.         AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);  
      109.         alphaAnimation.setDuration(2000);  
      110.         splash_rl.setAnimation(alphaAnimation);  
      111.           
      112.         /* 
      113.          * 这个时间值是用来控制Splash界面显示时间的 
      114.          * 记录下这个值, 然后执行到下面, 如果时间差在3秒以内,  
      115.          * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差 
      116.          * 等够3秒在执行下面的操作 
      117.          */  
      118.         time = System.currentTimeMillis();  
      119.   
      120.         //从SharedPreference中获取一些配置  
      121.         sp = getSharedPreferences("config", Context.MODE_PRIVATE);  
      122.           
      123.         //开启检查版本号线程  
      124.         new Thread(new CheckVersionTask()).start();  
      125.     }  
      126.       
      127.     private final class CheckVersionTask implements Runnable{  
      128.         public void run() {  
      129.             try {  
      130.                 /* 
      131.                  * 获取当前时间, 与onCreate方法中获取的时间进行比较 
      132.                  * 如果不足3秒, 在等待够3秒之后在执行下面的操作 
      133.                  */  
      134.                 long temp = System.currentTimeMillis();  
      135.                 if(temp - time < 3000){  
      136.                     SystemClock.sleep(temp - time);  
      137.                 }  
      138.                   
      139.                 /* 
      140.                  * 检查配置文件中的设置, 是否设置了自动更新;  
      141.                  * 如果设置了自动更新, 就执行下面的操作, 
      142.                  * 如果没有设置自动更新, 就直接进入主界面 
      143.                  */  
      144.                 boolean is_auto_update = sp.getBoolean("is_auto_update"true);  
      145.                 if(!is_auto_update){  
      146.                     loadMainUI();  
      147.                     return;  
      148.                 }  
      149.                   
      150.                 /* 
      151.                  * 获取更新信息 
      152.                  * 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作 
      153.                  * 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作 
      154.                  * 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作 
      155.                  */  
      156.                 updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);  
      157.                 if(updateInfo != null){  
      158.                     Message msg = new Message();  
      159.                     msg.what = SUCESS_GET_UPDATEINOF;  
      160.                     mHandler.sendMessage(msg);  
      161.                 }else{  
      162.                     Message msg = new Message();  
      163.                     msg.what = ERROR_GET_UPDATEINOF;  
      164.                     mHandler.sendMessage(msg);  
      165.                 }  
      166.             } catch (Exception e) {  
      167.                 e.printStackTrace();  
      168.                 Message msg = new Message();  
      169.                 msg.what = ERROR_GET_UPDATEINOF;  
      170.                 mHandler.sendMessage(msg);  
      171.             }  
      172.         }  
      173.     }  
      174.       
      175.     /** 
      176.      * 安装apk文件流程 
      177.      *  
      178.      * a. 设置Action : Intent.ACTION_VIEW. 
      179.      * b. 设置数据和类型 : 设置apk文件的uri 和 MIME类型 
      180.      * c. 开启安装文件的Activity. 
      181.      */  
      182.     protected void installApk() {  
      183.         Intent intent = new Intent();  
      184.         intent.setAction(Intent.ACTION_VIEW);  
      185.         intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");  
      186.         startActivity(intent);  
      187.     }  
      188.       
      189.     /** 
      190.      * 弹出更新对话框 
      191.      *  
      192.      * a. 创建builder对象 
      193.      * b. 设置标题 
      194.      * c. 设置对话框显示信息 
      195.      * d. 设置该对话框不可回退, 如果回退的话就会卡在本界面 
      196.      * e. 设置确定按钮 
      197.      * f. 设置取消按钮 
      198.      * g. 创建对话框 
      199.      * h. 显示对话框 
      200.      *  
      201.      * 确定按钮按下显示进度条对话框 
      202.      * a. 创建一个进度条对话框 
      203.      * b. 设置该对话框不能回退 
      204.      * c. 设置进度条样式 
      205.      * d. 设置进度条的信息 
      206.      * e. 显示进度条对话框 
      207.      * f. 开启一个线程, 下载apk文件 
      208.      */  
      209.     protected void showUpdateDialog() {  
      210.         //创建builder对象  
      211.         AlertDialog.Builder builder = new AlertDialog.Builder(this);  
      212.         //设置标题  
      213.         builder.setTitle(getString(R.string.update_dialog_tittle));  
      214.         //设置对话框信息  
      215.         builder.setMessage(updateInfo.getDescription());  
      216.         //设置不可回退  
      217.         builder.setCancelable(false);  
      218.         //设置确定按钮  
      219.         builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {  
      220.             public void onClick(DialogInterface dialog, int which) {  
      221.                 //创建进度条对话框  
      222.                 mPb = new ProgressDialog(SplashActivity.this);  
      223.                 //设置进度条对话框不可回退  
      224.                 mPb.setCancelable(false);  
      225.                 //设置进度条对话框样式  
      226.                 mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);  
      227.                 //设置进度条对话框的信息  
      228.                 mPb.setMessage(getString(R.string.update_dialog_messsage));  
      229.                 //显示进度条对话框  
      230.                 mPb.show();  
      231.                 //开启显示进度条对话框线程  
      232.                 new Thread(new DownloadApkTask()).start();  
      233.             }  
      234.         });  
      235.         builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {  
      236.             public void onClick(DialogInterface dialog, int which) {  
      237.                 loadMainUI();  
      238.             }  
      239.         });  
      240.         //创建更新信息提示对话框  
      241.         mUpdateInfoDialog = builder.create();  
      242.         //显示更新信息提示对话框  
      243.         mUpdateInfoDialog.show();  
      244.     }  
      245.       
      246.     /** 
      247.      * 在这个线程中主要执行downloadApk方法, 这个方法传入apk路径和进度条对话框 
      248.      * 注意 : 下载的前提是sd卡的状态是挂载的 
      249.      */  
      250.     private final class DownloadApkTask implements Runnable{  
      251.         public void run() {  
      252.             if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){  
      253.                 try {  
      254.                     SystemClock.sleep(2000);  
      255.                     apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);  
      256.                     Message msg = new Message();  
      257.                     msg.what = SUCCESS_DOWNLOAD_APK;  
      258.                     mHandler.sendMessage(msg);  
      259.                 } catch (Exception e) {  
      260.                     e.printStackTrace();  
      261.                     Message msg = new Message();  
      262.                     msg.what = ERROR_DOWNLOAD_APK;  
      263.                     mHandler.sendMessage(msg);  
      264.                 }  
      265.             }  
      266.         }  
      267.     }  
      268.       
      269.     /** 
      270.      * 下载apk更新文件 
      271.      *   
      272.      * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件 
      273.      * b. 创建URL对象 
      274.      * c. 创建HttpUrlConnection对象 
      275.      * d. 设置链接对象超时时间 
      276.      * e. 设置请求方式 get 
      277.      * f. 如果请求成功执行下面的操作 
      278.      *  
      279.      * g. 通过链接对象获取网络资源的大小 
      280.      * h. 将文件大小设置给进度条对话框 
      281.      * i. 获取输入流, 并且读取输入流信息 
      282.      * j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框 
      283.      */  
      284.     public File downloadApk(String path,ProgressDialog pb) throws Exception{  
      285.         //创建本地文件对象  
      286.         File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));  
      287.         //创建HttpURL连接  
      288.         URL url = new URL(path);  
      289.         HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
      290.         conn.setConnectTimeout(5000);  
      291.         conn.setRequestMethod("GET");  
      292.         if(conn.getResponseCode() == 200){  
      293.             int max = conn.getContentLength();  
      294.             //设置进度条对话框的最大值  
      295.             pb.setMax(max);  
      296.             int count = 0;  
      297.             InputStream is = conn.getInputStream();  
      298.             FileOutputStream fos = new FileOutputStream(file);  
      299.             byte[] buffer = new byte[1024];  
      300.             int len = 0;  
      301.             while((len = is.read(buffer)) != -1){  
      302.                 fos.write(buffer, 0, len);  
      303.                 //设置进度条对话框进度  
      304.                 count = count + len;  
      305.                 pb.setProgress(count);  
      306.             }  
      307.             is.close();  
      308.             fos.close();  
      309.         }  
      310.         return file;  
      311.     }  
      312.       
      313.     private String getFileName(String path){  
      314.         return path.substring(path.lastIndexOf("/") + 1);  
      315.     }  
      316.       
      317.     private String getVersion() {  
      318.         try {  
      319.             pm = this.getPackageManager();  
      320.             PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);  
      321.             return packageInfo.versionName;  
      322.         } catch (Exception e) {  
      323.             e.printStackTrace();  
      324.         }  
      325.         return null;  
      326.     }  
      327.       
      328.     /** 
      329.      * 获取更新信息 
      330.      *      ① 根据字符串地址创建URL对象 
      331.      *      ② 根据URL对象创建HttpURLConnection链接对象 
      332.      *      ③ 设置链接对象5秒超时 
      333.      *      ④ 设置链接对象获取的方式为get方式 
      334.      *      ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流 
      335.      *      ⑥ 解析输入流获取更新信息 
      336.      *       
      337.      */  
      338.     private UpdateInfo getUpdateInfo(String path){  
      339.         try {  
      340.             URL url = new URL(path);    //创建URL对象  
      341.             //创建连接对象  
      342.             HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
      343.             //设置链接超时  
      344.             conn.setConnectTimeout(5000);  
      345.             //设置获取方式  
      346.             conn.setRequestMethod("GET");  
      347.             //如果连接成功, 获取输入流  
      348.             if(conn.getResponseCode() == 200){  
      349.                 InputStream is = conn.getInputStream();  
      350.                 //解析输入流中的数据, 返回更新信息  
      351.                 return parserUpdateInfo(is);  
      352.             }  
      353.         } catch (MalformedURLException e) {  
      354.             e.printStackTrace();  
      355.         } catch (ProtocolException e) {  
      356.             e.printStackTrace();  
      357.         } catch (IOException e) {  
      358.             e.printStackTrace();  
      359.         }  
      360.         return null;  
      361.     }  
      362.       
      363.     /** 
      364.      * 获取更新信息 
      365.      *      ① 创建pull解析器 
      366.      *      ② 为解析器设置编码格式 
      367.      *      ③ 获取解析事件 
      368.      *      ④ 遍历整个xml文件节点, 获取标签元素内容 
      369.      */  
      370.     private UpdateInfo parserUpdateInfo(InputStream is){  
      371.         try {  
      372.             UpdateInfo updateInfo = null;  
      373.             //1. 创建pull解析解析器  
      374.             XmlPullParser parser = Xml.newPullParser();  
      375.             //2. 设置解析编码  
      376.             parser.setInput(is, "UTF-8");  
      377.             //3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等  
      378.             int eventType = parser.getEventType();  
      379.             //4. 在文档结束前一直解析  
      380.             while (eventType != XmlPullParser.END_DOCUMENT) {  
      381.                 switch (eventType) {  
      382.                 //只解析标签  
      383.                 case XmlPullParser.START_TAG:  
      384.                     if ("updateInfo".equals(parser.getName())) {  
      385.                         //当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象  
      386.                         updateInfo = new UpdateInfo();  
      387.                     } else if ("version".equals(parser.getName())) {  
      388.                         //解析版本号标签  
      389.                         updateInfo.setVersion(parser.nextText());  
      390.                     } else if ("url".equals(parser.getName())) {  
      391.                         //解析url标签  
      392.                         updateInfo.setUrl(parser.nextText());  
      393.                     } else if ("description".equals(parser.getName())) {  
      394.                         //解析描述标签  
      395.                         updateInfo.setDescription(parser.nextText());  
      396.                     }  
      397.                     break;  
      398.                 default:  
      399.                     break;  
      400.                 }  
      401.                 //每解析完一个元素, 就将解析标志位下移  
      402.                 eventType = parser.next();  
      403.             }  
      404.             is.close();  
      405.             return updateInfo;  
      406.         } catch (XmlPullParserException e) {  
      407.             e.printStackTrace();  
      408.         } catch (IOException e) {  
      409.             e.printStackTrace();  
      410.         }  
      411.         return null;  
      412.     }  
      413.       
      414.     private void loadMainUI(){  
      415.         Intent intent = new Intent(this,HomeActivity.class);  
      416.         startActivity(intent);  
      417.         finish();  
      418.         overridePendingTransition(R.anim.main_in, R.anim.splash_out);  
      419.     }  
      420.       
      421.       
      422.     public class UpdateInfo {  
      423.         private String version; //当前软件版本号  
      424.         private String url;     //获取到的软件地址  
      425.         private String description; //软件描述  
      426.           
      427.         public String getVersion() {  
      428.             return version;  
      429.         }  
      430.         public void setVersion(String version) {  
      431.             this.version = version;  
      432.         }  
      433.         public String getUrl() {  
      434.             return url;  
      435.         }  
      436.         public void setUrl(String url) {  
      437.             this.url = url;  
      438.         }  
      439.         public String getDescription() {  
      440.             return description;  
      441.         }  
      442.         public void setDescription(String description) {  
      443.             this.description = description;  
      444.         }  
      445.         @Override  
      446.         public String toString() {  
      447.             return "UpdateInfo [version=" + version + ", url=" + url  
      448.                     + ", description=" + description + "]";  
      449.         }  
      450.     }  
      451. }  
  • 相关阅读:
    POJ2155 Matrix 【二维线段树】
    BZOJ4785 [Zjoi2017]树状数组 【二维线段树 + 标记永久化】
    B1027 打印沙漏
    Tomcat无法成功启动——双击startup.bat闪退
    MySQL在cmd命令行查看端口号
    1009 说反话(类似回文字符串)
    除基取余法,
    日期差值
    怎么把VS里的scanf_s换成scanf
    联想小新潮怎么修改fn热键以及怎么进入bios状态
  • 原文地址:https://www.cnblogs.com/android100/p/android-auto-update.html
Copyright © 2011-2022 走看看