Android笔记----app应用更新

问题引入:
最近实习的项目要实现一个需求,实现app检测更新并且覆盖安装。

  • 简单思路:
    1.听说现成也有一些热更新插件轮子可以搬,直接使用插件就可以。(网页与原生混合开发比较适合)
    2.另一个想法是,在本地应用定一个版本号,在远程服务端定义一个版本号,并且放置安装apk.执行,版本比较并且实现下载安装。
    思路还是比较简单的,但是实现过程却走了一些坑。(适合原生Java开发)

  • 遇到的坑:
    由于实现思路是前人想的,他们才是真正造轮子的人,要感谢下面几位博客贡献源:

    1. 首先是调试的问题,如上我们如果要检测版本必须要有个远程服务器进行版本比较,这就涉及到服务器的搭建,如果没有购买服务器,一般我是用tomcat来进行搭建,那么真机要如何访问到这个地址呢,要知道,本地搭建的url在不同模拟器上是不一样的,真机,genymotion和AS自带AVD又是不一样的访问方式。
      所以首先解决访问问题要感谢下面这位博主:
      http://supportopensource.iteye.com/blog/1959711

      • 真机调试,自己电脑搭个tomcat,那么真机访问到要连接同个局域网wifi并且 通过本机IP:8080/访问
      • 模拟器,通过10.0.2.2:8080/访问
    2. 文件读写权限问题
      在棉花糖之前(Android 6.0),Android权限获取是一刀切模式,就是说安装app之后会提醒是否信任该程序,一旦信任,就获取所有权限。在6.0之后,为了提高安全性,实行运行时权限,就是待需要申请那个权限,就弹出申请框提示用户授权。关于Android不同版本的权限问题,又是另外一个话题,可以参考搜索:Android6.0运行时权限。
      我们在Androidimanifest.xml加的权限请求是为了对付6.0之前的一刀切获取权限情况。
    <uses-permission android:name="android.permission.INTERNET" >uses-permission>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUT_FILESYSTEMS"/>

另外,当用户拒绝超过两次之后,权限申请框会有个不再提示选项,一旦用户点击不再提示,那么程序再也无法调用申请,只能提示用户去设置界面对app进行手动信任,这也是一个程序友好性以及健壮性有待提高的地方- -。
3. 文件存取路径问题
Android在手机端的文件系统分为外部存储和内部存储, 但是,中文经常听到内存和外存SD卡,其实内存和SD卡并不是内部存储和外部存储的区分方式。
具体可以参考:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0923/1557.html


  • 关键代码:
    轮子作者源地址:
    http://blog.csdn.net/victoryckl/article/details/6892653

本地版本为1.0,放在Androidmanifest.xml

"http://schemas.android.com/apk/res/android"
    xmlns:Android="http://schemas.android.com/apk/res-auto"
    package="com.example.pixelpig.update_demo_"
    android:versionCode="1"
    android:versionName="1.0">

服务器那边放置待下载的apk,和一个version.xml方便版本比较以及更新特性描述。

<info>
<script/>
<version>2.0version>
<url>http://101.201.64.95:8080/pp/t.apkurl>
<description>检测到最新版本,请及时更新! 1.SADFSADF 2.@#FSADFFdescription>
info>
//权限检测
    public static final int EXTERNAL_STORAGE_REQ_CODE = 10 ;

    public void requestPermission(){
        //判断当前Activity是否已经获得了该权限
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {

            //当用户拒绝权限之后,再次调用对用户做出解释
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Log.i(TAG,"曾经申请,被用户禁止权限(还未点击不再提示)");
                Toast.makeText(this, "为了正常更新,请授予该应用读写文件权限。", Toast.LENGTH_SHORT).show();
                //进行权限请求
            }else{
                Log.i(TAG, "第一次申请");
            }
            Log.i(TAG, "获取权限");
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    EXTERNAL_STORAGE_REQ_CODE);

        }else{
            //已经有权限,下载
            downLoadApk();
        }
    }

//    用户经过requestPermission()选择允许或拒绝后,会回调此onRequestPermissionsResult方法
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case EXTERNAL_STORAGE_REQ_CODE: {
                // 如果请求被拒绝,那么通常grantResults数组为空
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //权限申请成功,进行相应操作
                    Log.i(TAG,"权限GET,下载apk,更新");
                    downLoadApk();
                } else {
                    Toast.makeText(this,"未获得权限,更新失效。",Toast.LENGTH_SHORT).show();
                    //等待用户重新申请,如果  用户永久拒绝,则需要手动更改。
                }
                return;
            }
        }
    }

    /*
     *
     * 弹出对话框通知用户更新程序
     *
     * 弹出对话框的步骤:
     *  1.创建alertDialog的builder.
     *  2.要给builder设置属性, 对话框的内容,样式,按钮
     *  3.通过builder 创建一个对话框
     *  4.对话框show()出来
     */
    protected void showUpdataDialog() {
        AlertDialog.Builder builer = new AlertDialog.Builder(this) ;
        builer.setTitle("当前版本:"+ Local_verson_No +"   新版本:"+New_verson_NO);
        builer.setMessage(description);//info.getDescription());
        //当点确定按钮时从服务器上下载 新的apk 然后安装
        builer.setPositiveButton("下载安装", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                Log.i(TAG,"检测权限");
                requestPermission();
            }
        });
        //当点取消按钮时忽略
        builer.setNegativeButton("取消更新", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                Log.i(TAG,"取消更新");
                dialog.dismiss();//dismiss返回取消值,区别于cancel() ,cancel类似于按back键,不返回值
            }
        });
        AlertDialog dialog = builer.create();
        dialog.show();
    }

    //获取本地Manifest记录版本号
    private String getLocalVersion(){
        PackageInfo pkg;
        String versionName = null;
        try {
            pkg = getPackageManager().getPackageInfo(getApplication().getPackageName(), 0);
            String appName = pkg.applicationInfo.loadLabel(getPackageManager()).toString();
            versionName = pkg.versionName;
            Log.d(TAG,"appName:" + appName);
            Log.d(TAG,"versionName:" + versionName);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return versionName;
    }

    //获取服务器版本号
    public String getHttpVersion(){

        //在子线程中获取服务器的数据
        Thread thread = new Thread(){
            @Override
            public void run() {

                try {
                    URL url = new URL(path);
                    //建立连接
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    //设置请求方式
                    conn.setRequestMethod("GET");
                    //设置请求超时时间
                    conn.setConnectTimeout(5000);
                    //设置读取超时时间
                    conn.setReadTimeout(5000);
                    //判断是否获取成功
                    if(conn.getResponseCode() == 200)
                    {
                        Log.d(TAG,"连接服务器成功");
                        //获得输入流
                        InputStream is = conn.getInputStream();
                        //解析输入流中的数据
                        New_verson_NO = parseXmlInfo(is);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        //启动线程
        thread.start();
        return  New_verson_NO;
    }

    //解析xml
    public String parseXmlInfo(InputStream is)
    {
        UpdataInfo info = new UpdataInfo();//实体

        /*我们用pull解析器解析xml文件*/

        //1.先拿到pull解析器
        XmlPullParser xParser = Xml.newPullParser();

        try {
            xParser.setInput(is, "utf-8");
            //获取事件的类型
            int eventType = xParser.getEventType();

            while(eventType != XmlPullParser.END_DOCUMENT)
            {
                switch (eventType) {
                    case XmlPullParser.START_TAG:
                        //当事件的开始类型newslist,代表的是xml文件的数据开始
                        if("version".equals(xParser.getName())){
                            info.setVersion(xParser.nextText()); //获取版本号
                        }else if ("url".equals(xParser.getName())){
                            info.setUrl(xParser.nextText()); //获取要升级的APK文件
                        }else if ("description".equals(xParser.getName())){
                            info.setDescription(xParser.nextText()); //获取该文件的信息
                        }
                        break;
                }
                eventType = xParser.next();
            }
            //打印服务器各项信息
            Log.d(TAG,info.getVersion() + info.getDescription() + info.getUrl());
            //全局变量赋值
            description = info.getDescription();
            //获取服务器安装包存放的地址
            apk_url = info.getUrl();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return info.getVersion();
    }


//    从服务器中下载APK
    protected void downLoadApk() {
        final ProgressDialog pd;    //进度条对话框
        pd = new  ProgressDialog(this);
        pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        pd.setMessage("正在下载更新...");
        pd.show();

        new Thread(){
            @Override
            public void run() {
                try {
                    File file = getFileFromServer(apk_url, pd);
                    sleep(3000);
                    installApk(file);
                    pd.dismiss(); //结束掉进度条对话框
                } catch (Exception e) {
                    Message msg = new Message();
                    msg.what = -1;
                    handler.sendMessage(msg);
                    e.printStackTrace();
                }
            }}.start();
    }

    Handler handler = new Handler();

    //执行安装apk
    protected void installApk(File file) {
        Intent intent = new Intent();
        //执行动作
        intent.setAction(Intent.ACTION_VIEW);
        //执行的数据类型   针对apk: {".apk",    "application/vnd.android.package-archive"}
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        startActivity(intent);
        Log.i(TAG,"______________________执行安装apk");
    }

    public static File getFileFromServer(String path, ProgressDialog pd) throws Exception{
        //如果相等的话表示当前的手机上内部存储存在且可用的
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            Log.i(TAG,"______________________手机内部存储可用");
            URL url = new URL(path);
            HttpURLConnection conn =  (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            //获取到文件的大小
            pd.setMax(conn.getContentLength());
            InputStream is = conn.getInputStream();
            //此处填写安装包名称以及存取位置,下方表示存在手机内部存储根目录,以t.apk命名
            Log.i(TAG,"______________________存取手机内部存储根目录");
            File file = new File(Environment.getExternalStorageDirectory(), "t.apk");
            FileOutputStream fos = new FileOutputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            byte[] buffer = new byte[1024];
            int len ;
            int total=0;
            while((len =bis.read(buffer))!=-1){
                fos.write(buffer, 0, len);
                total+= len;
                //获取当前下载量,更新进度条
                pd.setProgress(total);
            }
            fos.close();
            bis.close();
            is.close();
            return file;
        }
        else{
            return null;
        }
    }
  • 结果示例:

你可能感兴趣的:(Android)