Android App 自动更新功能

发布时间 2023-04-12 15:03:43作者: 不及格的程序员-八神

简单实现安卓app自动更新功能

12016.04.26 08:48:29字数 754阅读 42,477

简单实现安卓自动更新

一般的安卓app都有自动更新功能,实现app的更新,以让用户体验新版本的功能,这里也是项目中用到的,今天就来总结一下,代码应该有点多,还请耐心点哈。
安卓应用实现自动更新比较简单,这里跟大家介绍下:

第一步 服务器端:

  • 服务端提供一个借口,或者网址,我这里就用的服务器是tomcat,这里提供一个网址如下:
//也就是一个json数据接口
  public static final String UPDATE_URL = "http://192.168.1.103:8080/update.json";

我们来看下json数据参数:

{
//app名字
appname: "爱新闻1.1",
//服务器版本号
serverVersion: "2",
//服务器标志
serverFlag: "1",
//是否强制更新
lastForce: "1",
//apk下载地址,这里我已经下载了官方的apk,放到了服务器里面
updateurl: "http://192.168.1.103:8080/36Kr.apk",
//版本的更新的描述
upgradeinfo: "V1.1版本更新,你想不想要试一下哈!!!"
}

好了以上的是服务器端的信息,在这里不需要多说了,我们来看下客户端的吧。

第二步 客户端需要实现:

首先我们要去解析服务端给的json,那么我们就要来创建一个model类了(代码过多,这里只有字段,getter和setter方法自己创建):

//app名字
    private String appname;
    //服务器版本
    private String serverVersion;
    //服务器标志
    private String serverFlag;
    //强制升级
    private String lastForce;
    //app最新版本地址
    private String updateurl;
    //升级信息
    private String upgradeinfo;

在这里使用了一个辅助类,基本和model字段差不多:

public class UpdateInformation {
    public static String appname = MyApplication.getInstance()
            .getResources().getString(R.string.app_name);
    public static int localVersion = 1;// 本地版本
    public static String versionName = ""; // 本地版本名
    public static int serverVersion = 1;// 服务器版本
    public static int serverFlag = 0;// 服务器标志
    public static int lastForce = 0;// 之前强制升级版本
    public static String updateurl = "";// 升级包获取地址
    public static String upgradeinfo = "";// 升级信息

    public static String downloadDir = "wuyinlei";// 下载目录
}

我们知道,我们在进入app的时候,这个时候如果检测到服务器端有了新的版本,就回弹出提示框,提示我们更新。这个我们在MainActivity里面处理逻辑(onCreate()方法里面):

 OkhttpManager.getAsync(Config.UPDATE_URL, new OkhttpManager.DataCallBack() {
            @Override
            public void requestFailure(Request request, Exception e) {

            }
            @Override
            public void requestSuccess(String result) {
                try {
                    Log.d("wuyiunlei",result);
                    JSONObject object = new JSONObject(result);
                    UpdateInfoModel model = new UpdateInfoModel();
                    model.setAppname(object.getString("appname"));
                    model.setLastForce(object.getString("lastForce"));
                    model.setServerFlag(object.getString("serverFlag"));
                    model.setServerVersion(object.getString("serverVersion"));
                    model.setUpdateurl(object.getString("updateurl"));
                    model.setUpgradeinfo(object.getString("upgradeinfo"));
                    tmpMap.put(DeliverConsts.KEY_APP_UPDATE, model);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                //发送广播
                sendBroadcast(new Intent(UpdateReceiver.UPDATE_ACTION));
            }
        });

当然了,我们也要注册和结束广播:

 /**
     * 广播注册
     */
    private void registerBroadcast() {
        mUpdateReceiver = new UpdateReceiver(false);
        mIntentFilter = new IntentFilter(UpdateReceiver.UPDATE_ACTION);
        this.registerReceiver(mUpdateReceiver, mIntentFilter);
    }
 /**
     * 广播卸载
     */
    private void unRegisterBroadcast() {
        try {
            this.unregisterReceiver(mUpdateReceiver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

好了,接下来我们看下我们自定义的广播接收者UpdateReceiver .java:


/**
 * 版本更新升级 广播接受者
 *
 */
public class UpdateReceiver extends BroadcastReceiver {
    private AlertDialog.Builder mDialog;
    public static final String UPDATE_ACTION = "wuyinlei_aixinwen";
    private SharedPreferencesHelper mSharedPreferencesHelper;
    private boolean isShowDialog;

    public UpdateReceiver() {
    }

    public UpdateReceiver(boolean isShowDialog) {
        super();
        this.isShowDialog = isShowDialog;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        mSharedPreferencesHelper = mSharedPreferencesHelper
                .getInstance(MyApplication.getInstance());
        //当然了,这里也可以直接new处hashmap
        HashMap<String, Object> tempMap = MyApplication.getInstance()
                .getTempMap();
        UpdateInfoModel model = (UpdateInfoModel) tempMap
                //就是一个标志
                .get(DeliverConsts.KEY_APP_UPDATE);
        try {

            /**
             * 获取到当前的本地版本
             */
            UpdateInformation.localVersion = MyApplication
                    .getInstance()
                    //包管理独享
                    .getPackageManager()
                    //包信息
                    .getPackageInfo(
                            MyApplication.getInstance()
                                    .getPackageName(), 0).versionCode;
            /**
             * 获取到当前的版本名字
             */
            UpdateInformation.versionName = MyApplication
                    .getInstance()
                    .getPackageManager()
                    .getPackageInfo(
                            MyApplication.getInstance()
                                    .getPackageName(), 0).versionName;
        } catch (Exception e) {
            e.printStackTrace();
        }
        //app名字
        UpdateInformation.appname = MyApplication.getInstance()
                .getResources().getString(R.string.app_name);
        //服务器版本
        UpdateInformation.serverVersion = Integer.parseInt(model
                .getServerVersion());
        //服务器标志
        UpdateInformation.serverFlag = Integer.parseInt(model.getServerFlag());
        //强制升级
        UpdateInformation.lastForce = Integer.parseInt(model.getLastForce());
        //升级地址
        UpdateInformation.updateurl = model.getUpdateurl();
        //升级信息
        UpdateInformation.upgradeinfo = model.getUpgradeinfo();

        //检查版本
        checkVersion(context);

    }

    /**
     * 检查版本更新
     * 
     * @param context
     */
    public void checkVersion(Context context) {
        if (UpdateInformation.localVersion < UpdateInformation.serverVersion) {
            // 需要进行更新
            mSharedPreferencesHelper.putIntValue(
                    //有新版本
                    SharedPreferencesTag.IS_HAVE_NEW_VERSION, 1);
            //更新
            update(context);
        } else {
            mSharedPreferencesHelper.putIntValue(
                    SharedPreferencesTag.IS_HAVE_NEW_VERSION, 0);
            if (isShowDialog) {
                //没有最新版本,不用升级
                noNewVersion(context);
            }
            clearUpateFile(context);
        }
    }

    /**
     * 进行升级
     * 
     * @param context
     */
    private void update(Context context) {
        if (UpdateInformation.serverFlag == 1) {
            // 官方推荐升级
            if (UpdateInformation.localVersion < UpdateInformation.lastForce) {
                //强制升级
                forceUpdate(context);
            } else {
                //正常升级
                normalUpdate(context);
            }

        } else if (UpdateInformation.serverFlag == 2) {
            // 官方强制升级
            forceUpdate(context);
        }
    }

    /**
     * 没有新版本
     * @param context
     */
    private void noNewVersion(final Context context) {
        mDialog = new AlertDialog.Builder(context);
        mDialog.setTitle("版本更新");
        mDialog.setMessage("当前为最新版本");
        mDialog.setNegativeButton("确定", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        }).create().show();
    }

    /**
     * 强制升级 ,如果不点击确定升级,直接退出应用
     * 
     * @param context
     */
    private void forceUpdate(final Context context) {
        mDialog = new AlertDialog.Builder(context);
        mDialog.setTitle("版本更新");
        mDialog.setMessage(UpdateInformation.upgradeinfo);

        mDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent mIntent = new Intent(context, UpdateService.class);
                mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                mIntent.putExtra("appname", UpdateInformation.appname);
                mIntent.putExtra("appurl", UpdateInformation.updateurl);
                //启动服务
                context.startService(mIntent);
            }
        }).setNegativeButton("退出", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // 直接退出应用
                //ManagerActivity.getInstance().finishActivity();
                System.exit(0);
            }
        }).setCancelable(false).create().show();
    }

    /**
     * 正常升级,用户可以选择是否取消升级
     * 
     * @param context
     */
    private void normalUpdate(final Context context) {
        mDialog = new AlertDialog.Builder(context);
        mDialog.setTitle("版本更新");
        mDialog.setMessage(UpdateInformation.upgradeinfo);
        mDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent mIntent = new Intent(context, UpdateService.class);
                mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                //传递数据
                mIntent.putExtra("appname", UpdateInformation.appname);
                mIntent.putExtra("appurl", UpdateInformation.updateurl);
                context.startService(mIntent);
            }
        }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        }).create().show();
    }

    /**
     * 清理升级文件
     * 
     * @param context
     */
    private void clearUpateFile(final Context context) {
        File updateDir;
        File updateFile;
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())) {
            updateDir = new File(Environment.getExternalStorageDirectory(),
                    UpdateInformation.downloadDir);
        } else {
            updateDir = context.getFilesDir();
        }
        updateFile = new File(updateDir.getPath(), context.getResources()
                .getString(R.string.app_name) + ".apk");
        if (updateFile.exists()) {
            Log.d("update", "升级包存在,删除升级包");
            updateFile.delete();
        } else {
            Log.d("update", "升级包不存在,不用删除升级包");
        }
    }
}

接下最后我们来看下服务吧UpdateService .java:

/**
 * 不要忘记注册,在mainfest文件中
*/
public class UpdateService extends Service {
    // BT字节参考量
    private static final float SIZE_BT = 1024L;
    // KB字节参考量
    private static final float SIZE_KB = SIZE_BT * 1024.0f;
    // MB字节参考量
    private static final float SIZE_MB = SIZE_KB * 1024.0f;

    private final static int DOWNLOAD_COMPLETE = 1;// 完成
    private final static int DOWNLOAD_NOMEMORY = -1;// 内存异常
    private final static int DOWNLOAD_FAIL = -2;// 失败

    private String appName = null;// 应用名字
    private String appUrl = null;// 应用升级地址
    private File updateDir = null;// 文件目录
    private File updateFile = null;// 升级文件

    // 通知栏
    private NotificationManager updateNotificationManager = null;
    private Notification updateNotification = null;

    private Intent updateIntent = null;// 下载完成
    private PendingIntent updatePendingIntent = null;// 在下载的时候

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        appName = intent.getStringExtra("appname");
        appUrl = intent.getStringExtra("appurl");
        updateNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        updateNotification = new Notification();
        //通知图标
        updateNotification.icon = R.mipmap.head;
        //通知信息描述
        updateNotification.tickerText = "正在下载 " + appName;
        updateNotification.when = System.currentTimeMillis();
        updateIntent = new Intent(this, MyApplication.class);
        updatePendingIntent = PendingIntent.getActivity(this, 0, updateIntent,
                0);
        updateNotification.contentIntent = updatePendingIntent;
        updateNotification.contentIntent.cancel();
        updateNotification.contentView = new RemoteViews(getPackageName(),
                //这个布局很简单,就是一个图片和两个textview,分别是正在下载和下载进度
                R.layout.download_notification);
        updateNotification.contentView.setTextViewText(
                R.id.download_notice_name_tv, appName + " 正在下载");
        updateNotification.contentView.setTextViewText(
                R.id.download_notice_speed_tv, "0MB (0%)");
        updateNotificationManager.notify(0, updateNotification);
        new UpdateThread().execute();
    }

    /**
     * 在这里使用了asynctask异步任务来下载
     */
    class UpdateThread extends AsyncTask<Void, Void, Integer> {
        @Override
        protected Integer doInBackground(Void... params) {
            return downloadUpdateFile(appUrl);
        }

        @Override
        protected void onPostExecute(Integer result) {
            super.onPostExecute(result);

            if (result == DOWNLOAD_COMPLETE) {
                Log.d("update", "下载成功");
                String cmd = "chmod 777 " + updateFile.getPath();
                try {
                    Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                Uri uri = Uri.fromFile(updateFile);
                //安装程序
                Intent installIntent = new Intent(Intent.ACTION_VIEW);
                installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                installIntent.setDataAndType(uri,
                        "application/vnd.android.package-archive");
                updatePendingIntent = PendingIntent.getActivity(
                        UpdateService.this, 0, installIntent, 0);
                updateNotification.contentIntent = updatePendingIntent;
                updateNotification.contentView.setTextViewText(
                        R.id.download_notice_speed_tv,
                        getString(R.string.update_notice_finish));
                updateNotification.tickerText = appName + "下载完成";
                updateNotification.when = System.currentTimeMillis();
                updateNotification.defaults = Notification.DEFAULT_SOUND;
                updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
                updateNotificationManager.notify(0, updateNotification);
                //启动安装程序
                UpdateService.this.startActivity(installIntent);
                stopSelf();
            } else if (result == DOWNLOAD_NOMEMORY) {
                //如果内存有问题
                updateNotification.tickerText = appName + "下载失败";
                updateNotification.when = System.currentTimeMillis();
                updateNotification.contentView.setTextViewText(
                        R.id.download_notice_speed_tv,
                        getString(R.string.update_notice_nomemory));
                updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
                updateNotification.defaults = Notification.DEFAULT_SOUND;
                updateNotificationManager.notify(0, updateNotification);
                stopSelf();
            } else if (result == DOWNLOAD_FAIL) {
                //下载失败
                updateNotification.tickerText = appName + "下载失败";
                updateNotification.when = System.currentTimeMillis();
                updateNotification.contentView.setTextViewText(
                        R.id.download_notice_speed_tv,
                        getString(R.string.update_notice_error));
                updateNotification.flags |= Notification.FLAG_AUTO_CANCEL;
                updateNotification.defaults = Notification.DEFAULT_SOUND;
                updateNotificationManager.notify(0, updateNotification);
                stopSelf();
            }
        }

    }

    /**
     * 下载更新程序文件
     * @param downloadUrl   下载地址
     * @return
     */
    private int downloadUpdateFile(String downloadUrl) {
        int count = 0;
        long totalSize = 0;   //总大小
        long downloadSize = 0;   //下载的大小
        URI uri = null;

        //这个已经舍弃了,要用的话,就要加上org.apache.http.legacy.jar这个jar包
        HttpGet httpGet = null;
        try {
            uri = new URI(downloadUrl);
            httpGet = new HttpGet(uri);
        } catch (URISyntaxException e) {
            String encodedUrl = downloadUrl.replace(' ', '+');
            httpGet = new HttpGet(encodedUrl);
            e.printStackTrace();
        }
        HttpClient httpClient = new DefaultHttpClient();
        HttpResponse httpResponse = null;
        FileOutputStream fos = null;
        InputStream is = null;
        try {
            httpResponse = httpClient.execute(httpGet);
            if (httpResponse != null) {
                int stateCode = httpResponse.getStatusLine().getStatusCode();
                if (stateCode == HttpStatus.SC_OK) {
                    HttpEntity entity = httpResponse.getEntity();
                    if (entity != null) {
                        totalSize = entity.getContentLength();
                        //如果内存可用
                        if (MemoryAvailable(totalSize)) {
                            is = entity.getContent();
                            if (is != null) {
                                fos = new FileOutputStream(updateFile, false);
                                byte buffer[] = new byte[4096];
                                int readsize = 0;
                                while ((readsize = is.read(buffer)) > 0) {
                                    fos.write(buffer, 0, readsize);
                                    downloadSize += readsize;
                                    if ((count == 0)
                                            || (int) (downloadSize * 100 / totalSize) >= count) {
                                        count += 5;
                                        updateNotification.contentView
                                                .setTextViewText(
                                                        R.id.download_notice_speed_tv,
                                                        getMsgSpeed(downloadSize,totalSize));
                                        updateNotificationManager.notify(0,
                                                updateNotification);
                                    }
                                }
                                fos.flush();
                                if (totalSize >= downloadSize) {
                                    return DOWNLOAD_COMPLETE;
                                } else {
                                    return DOWNLOAD_FAIL;
                                }
                            }
                        } else {
                            if (httpGet != null) {
                                httpGet.abort();
                            }
                            return DOWNLOAD_NOMEMORY;
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (httpClient != null) {
                httpClient.getConnectionManager().shutdown();
            }
        }
        return DOWNLOAD_FAIL;
    }

    /**
     * 可用内存大小
     * @param fileSize
     * @return
     */
    private boolean MemoryAvailable(long fileSize) {
        fileSize += (1024 << 10);
        if (MemoryStatus.externalMemoryAvailable()) {
            if ((MemoryStatus.getAvailableExternalMemorySize() <= fileSize)) {
                if ((MemoryStatus.getAvailableInternalMemorySize() > fileSize)) {
                    createFile(false);
                    return true;
                } else {
                    return false;
                }
            } else {
                createFile(true);
                return true;
            }
        } else {
            if (MemoryStatus.getAvailableInternalMemorySize() <= fileSize) {
                return false;
            } else {
                createFile(false);
                return true;
            }
        }
    }

    /**
     * 获取下载进度
     * @param downSize
     * @param allSize
     * @return
     */
    public static String getMsgSpeed(long downSize, long allSize) {
        StringBuffer sBuf = new StringBuffer();
        sBuf.append(getSize(downSize));
        sBuf.append("/");
        sBuf.append(getSize(allSize));
        sBuf.append(" ");
        sBuf.append(getPercentSize(downSize, allSize));
        return sBuf.toString();
    }

    /**
     * 获取大小
     * @param size
     * @return
     */
    public static String getSize(long size) {
        if (size >= 0 && size < SIZE_BT) {
            return (double) (Math.round(size * 10) / 10.0) + "B";
        } else if (size >= SIZE_BT && size < SIZE_KB) {
            return (double) (Math.round((size / SIZE_BT) * 10) / 10.0) + "KB";
        } else if (size >= SIZE_KB && size < SIZE_MB) {
            return (double) (Math.round((size / SIZE_KB) * 10) / 10.0) + "MB";
        }
        return "";
    }

    /**
     * 获取到当前的下载百分比
     * @param downSize   下载大小
     * @param allSize    总共大小
     * @return
     */
    public static String getPercentSize(long downSize, long allSize) {
        String percent = (allSize == 0 ? "0.0" : new DecimalFormat("0.0")
                .format((double) downSize / (double) allSize * 100));
        return "(" + percent + "%)";
    }


    /**
     * 创建file文件
     * @param sd_available    sdcard是否可用
     */
    private void createFile(boolean sd_available) {
        if (sd_available) {
            updateDir = new File(Environment.getExternalStorageDirectory(),
                    UpdateInformation.downloadDir);
        } else {
            updateDir = getFilesDir();
        }
        updateFile = new File(updateDir.getPath(), appName + ".apk");
        if (!updateDir.exists()) {
            updateDir.mkdirs();
        }
        if (!updateFile.exists()) {
            try {
                updateFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            updateFile.delete();
            try {
                updateFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这个时候,可能看到服务怎么这么多代码啊,我头都大了,不要着急,我们一步一步说明一下,这里逻辑很简单,就是在通知栏中,用到了通知,这个时候我们有三种情况,造成了我们好多代码的重复,(你也可以不必考虑那么多情况),还有,里面有了几个工具类,没有提取出来,分别是获取sdcard大小是否可用(创建文件夹),获取当前下载进度,获取应用大小,下载文件,这里也可以使用第三方框架来下载。
里面的重要的地方都有注释,如果有疑问,可用互相讨论一下。
这里我们就简单的上几张图看看吧:
提示更新图:

 

 
这里写图片描述

更新下载通知:

 

 
这里写图片描述

 

下载完成后安装图:

 

 
这里写图片描述


最新版应用主界面图(这里我下载的是36kr官方的app,我在应用中心下载好的,嘿嘿):

 
这里写图片描述

 

当然了哈,这里我写的还是有点问题的,每次进入都会提示,如果有必要,也可以实现是否要自动更新,用服务,也就是点击是否自动更新,如果不是自动更新,就不会去触发服务端接口信息,如果是自动更新,就去触发,来获取最新的app版本。


 

Android应用更新-自动检测版本及自动升级

0.5042017.03.17 13:32:17字数 943阅读 8,047

步骤:

  • 1.检测当前版本的信息AndroidManifest.xml-->manifest-->[Android]

  • 2.从服务器获取版本号(版本号存在于xml文件中)并与当前检测到的版本进行匹配,如果不匹配,提示用户进行升级,如果匹配则进入程序主界面。(demo中假设需要更新)

  • 3.当提示用户进行版本升级时,如果用户点击了“更新”,系统将自动从服务器上下载安装包并进行自动升级,如果点击取消将进入程序主界面。

效果图如下:

 
更新

 

 
下载1
 
下载2
 
安装

下面介绍一下代码的实现:

  • 1.获取应用的当前版本号,我是封装了一个工具类来获取
 // 获取本版本号,是否更新
        int vision = Tools.getVersion(this);

获取当前版本号工具类:


public class Tools {
    /**
     * 检查是否存在SDCard
     *
     * @return
     */
    public static boolean hasSdcard() {
        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 2 * 获取版本号 3 * @return 当前应用的版本号 4
     */
    public static int getVersion(Context context) {
        try {
            PackageManager manager = context.getPackageManager();
            PackageInfo info = manager.getPackageInfo(context.getPackageName(),
                    0);
            String version = info.versionName;
            int versioncode = info.versionCode;
            return versioncode;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
   
}
  • 2.获取服务器版本号,是否要更新(此处就是简单的网络请求拿到需要的数据即可,我是写了固定值)
 // 获取更新版本号
    private void getVersion(final int vision) {
//         {"data":{"content":"其他bug修复。","id":"2","api_key":"android",
//         // "version":"2.1"},"msg":"获取成功","status":1}
        String data = "";
        //网络请求获取当前版本号和下载链接
        //实际操作是从服务器获取
        //demo写死了

        String newversion = "2.1";//更新新的版本号
        String content = "\n" +
                "就不告诉你我们更新了什么-。-\n" +
                "\n" +
                "----------万能的分割线-----------\n" +
                "\n" +
                "(ㄒoㄒ) 被老板打了一顿,还是来告诉你吧:\n" +

                "1.下架商品误买了?恩。。。我搞了点小动作就不会出现了\n" +
                "2.侧边栏、弹框优化 —— 这个你自己去探索吧,总得留点悬念嘛-。-\n";//更新内容
        String url = "http://openbox.mobilem.360.cn/index/d/sid/3429345";//安装包下载地址

        double newversioncode = Double
                .parseDouble(newversion);
        int cc = (int) (newversioncode);

        System.out.println(newversion + "v" + vision + ",,"
                + cc);
        if (cc != vision) {
            if (vision < cc) {
                System.out.println(newversion + "v"
                        + vision);
                // 版本号不同
                ShowDialog(vision, newversion, content, url);
            }
        }
    }
  • 3.接下来就是下载文件了
    (1) 显示下载
    此处用的是自定义按钮:
 /**
     * 升级系统
     *
     * @param content
     * @param url
     */
    private void ShowDialog(int vision, String newversion, String content,
                            final String url) {
        final MaterialDialog dialog = new MaterialDialog(this);
        dialog.content(content).btnText("取消", "更新").title("版本更新 ")
                .titleTextSize(15f).show();
        dialog.setCanceledOnTouchOutside(false);
        dialog.setOnBtnClickL(new OnBtnClickL() {// left btn click listener
            @Override
            public void onBtnClick() {
                dialog.dismiss();
            }
        }, new OnBtnClickL() {// right btn click listener

            @Override
            public void onBtnClick() {
                dialog.dismiss();
                // pBar = new ProgressDialog(MainActivity.this,
                // R.style.dialog);
                pBar = new CommonProgressDialog(MainActivity.this);
                pBar.setCanceledOnTouchOutside(false);
                pBar.setTitle("正在下载");
                pBar.setCustomTitle(LayoutInflater.from(
                        MainActivity.this).inflate(
                        R.layout.title_dialog, null));
                pBar.setMessage("正在下载");
                pBar.setIndeterminate(true);
                pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                pBar.setCancelable(true);
                // downFile(URLData.DOWNLOAD_URL);
                final DownloadTask downloadTask = new DownloadTask(
                        MainActivity.this);
                downloadTask.execute(url);
                pBar.setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        downloadTask.cancel(true);
                    }
                });
            }
        });
    }

原生的按钮:

 new android.app.AlertDialog.Builder(this)
                .setTitle("版本更新")
                .setMessage(content)
                .setPositiveButton("更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        pBar = new CommonProgressDialog(MainActivity.this);
                        pBar.setCanceledOnTouchOutside(false);
                        pBar.setTitle("正在下载");
                        pBar.setCustomTitle(LayoutInflater.from(
                                MainActivity.this).inflate(
                                R.layout.title_dialog, null));
                        pBar.setMessage("正在下载");
                        pBar.setIndeterminate(true);
                        pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                        pBar.setCancelable(true);
                        // downFile(URLData.DOWNLOAD_URL);
                        final DownloadTask downloadTask = new DownloadTask(
                                MainActivity.this);
                        downloadTask.execute(url);
                        pBar.setOnCancelListener(new DialogInterface.OnCancelListener() {
                            @Override
                            public void onCancel(DialogInterface dialog) {
                                downloadTask.cancel(true);
                            }
                        });
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                })
                .show();

(2)通过异步任务实现进度++


    /**
     * 下载应用
     *
     * @author Administrator
     */
    class DownloadTask extends AsyncTask<String, Integer, String> {

        private Context context;
        private PowerManager.WakeLock mWakeLock;

        public DownloadTask(Context context) {
            this.context = context;
        }

        @Override
        protected String doInBackground(String... sUrl) {
            InputStream input = null;
            OutputStream output = null;
            HttpURLConnection connection = null;
            File file = null;
            try {
                URL url = new URL(sUrl[0]);
                connection = (HttpURLConnection) url.openConnection();
                connection.connect();
                // expect HTTP 200 OK, so we don't mistakenly save error
                // report
                // instead of the file
                if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    return "Server returned HTTP "
                            + connection.getResponseCode() + " "
                            + connection.getResponseMessage();
                }
                // this will be useful to display download percentage
                // might be -1: server did not report the length
                int fileLength = connection.getContentLength();
                if (Environment.getExternalStorageState().equals(
                        Environment.MEDIA_MOUNTED)) {
                    file = new File(Environment.getExternalStorageDirectory(),
                            DOWNLOAD_NAME);

                    if (!file.exists()) {
                        // 判断父文件夹是否存在
                        if (!file.getParentFile().exists()) {
                            file.getParentFile().mkdirs();
                        }
                    }

                } else {
                    Toast.makeText(MainActivity.this, "sd卡未挂载",
                            Toast.LENGTH_LONG).show();
                }
                input = connection.getInputStream();
                output = new FileOutputStream(file);
                byte data[] = new byte[4096];
                long total = 0;
                int count;
                while ((count = input.read(data)) != -1) {
                    // allow canceling with back button
                    if (isCancelled()) {
                        input.close();
                        return null;
                    }
                    total += count;
                    // publishing the progress....
                    if (fileLength > 0) // only if total length is known
                        publishProgress((int) (total * 100 / fileLength));
                    output.write(data, 0, count);

                }
            } catch (Exception e) {
                System.out.println(e.toString());
                return e.toString();

            } finally {
                try {
                    if (output != null)
                        output.close();
                    if (input != null)
                        input.close();
                } catch (IOException ignored) {
                }
                if (connection != null)
                    connection.disconnect();
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // take CPU lock to prevent CPU from going off if the user
            // presses the power button during download
            PowerManager pm = (PowerManager) context
                    .getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    getClass().getName());
            mWakeLock.acquire();
            pBar.show();
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            // if we get here, length is known, now set indeterminate to false
            pBar.setIndeterminate(false);
            pBar.setMax(100);
            pBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(String result) {
            mWakeLock.release();
            pBar.dismiss();
            if (result != null) {

//                // 申请多个权限。大神的界面
//                AndPermission.with(MainActivity.this)
//                        .requestCode(REQUEST_CODE_PERMISSION_OTHER)
//                        .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
//                        // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。
//                        .rationale(new RationaleListener() {
//                                       @Override
//                                       public void showRequestPermissionRationale(int requestCode, Rationale rationale) {
//                                           // 这里的对话框可以自定义,只要调用rationale.resume()就可以继续申请。
//                                           AndPermission.rationaleDialog(MainActivity.this, rationale).show();
//                                       }
//                                   }
//                        )
//                        .send();
                // 申请多个权限。
                AndPermission.with(MainActivity.this)
                        .requestCode(REQUEST_CODE_PERMISSION_SD)
                        .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
                        // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。
                        .rationale(rationaleListener
                        )
                        .send();


                Toast.makeText(context, "您未打开SD卡权限" + result, Toast.LENGTH_LONG).show();
            } else {
                // Toast.makeText(context, "File downloaded",
                // Toast.LENGTH_SHORT)
                // .show();
                update();
            }

        }
    }

此处下载apk文件,需要获取SD的读写权限(用的是严大的权限库)
权限库GitHub:https://github.com/yanzhenjie/AndPermission

  private static final int REQUEST_CODE_PERMISSION_SD = 101;

    private static final int REQUEST_CODE_SETTING = 300;
    private RationaleListener rationaleListener = new RationaleListener() {
        @Override
        public void showRequestPermissionRationale(int requestCode, final Rationale rationale) {
            // 这里使用自定义对话框,如果不想自定义,用AndPermission默认对话框:
            // AndPermission.rationaleDialog(Context, Rationale).show();

            // 自定义对话框。
            AlertDialog.build(MainActivity.this)
                    .setTitle(R.string.title_dialog)
                    .setMessage(R.string.message_permission_rationale)
                    .setPositiveButton(R.string.btn_dialog_yes_permission, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                            rationale.resume();
                        }
                    })

                    .setNegativeButton(R.string.btn_dialog_no_permission, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                            rationale.cancel();
                        }
                    })
                    .show();
        }
    };
    //----------------------------------SD权限----------------------------------//


    @PermissionYes(REQUEST_CODE_PERMISSION_SD)
    private void getMultiYes(List<String> grantedPermissions) {
        Toast.makeText(this, R.string.message_post_succeed, Toast.LENGTH_SHORT).show();
    }

    @PermissionNo(REQUEST_CODE_PERMISSION_SD)
    private void getMultiNo(List<String> deniedPermissions) {
        Toast.makeText(this, R.string.message_post_failed, Toast.LENGTH_SHORT).show();

        // 用户否勾选了不再提示并且拒绝了权限,那么提示用户到设置中授权。
        if (AndPermission.hasAlwaysDeniedPermission(this, deniedPermissions)) {
            AndPermission.defaultSettingDialog(this, REQUEST_CODE_SETTING)
                    .setTitle(R.string.title_dialog)
                    .setMessage(R.string.message_permission_failed)
                    .setPositiveButton(R.string.btn_dialog_yes_permission)
                    .setNegativeButton(R.string.btn_dialog_no_permission, null)
                    .show();

            // 更多自定dialog,请看上面。
        }
    }

    //----------------------------------权限回调处理----------------------------------//

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
            grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        /**
         * 转给AndPermission分析结果。
         *
         * @param object     要接受结果的Activity、Fragment。
         * @param requestCode  请求码。
         * @param permissions  权限数组,一个或者多个。
         * @param grantResults 请求结果。
         */
        AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CODE_SETTING: {
                Toast.makeText(this, R.string.message_setting_back, Toast.LENGTH_LONG).show();
                //设置成功,再次请求更新
                getVersion(Tools.getVersion(MainActivity.this));
                break;
            }
        }
    }

(3) 当apk文件下载完毕时,打开安装

   private void update() {
        //安装应用
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(new File(Environment
                        .getExternalStorageDirectory(), DOWNLOAD_NAME)),
                "application/vnd.android.package-archive");
        startActivity(intent);
    }

Android 7.0 FileUriExposedException 的处理

发现问题

前几天把手机系统升级到基于 Android 7.0,后来在升级调试一个应用时抛出如下异常信息:

android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.skyrin.bingo/cache/app/app.apk exposed beyond app through Intent.getData()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)

at com.skyrin.bingo.update.AppUpdate.installApk(AppUpdate.java:295)

根据如上日志找到 AppUpdate 类下的 installApk 方法:

/**
 * 安装apk
 */
public static void installApk(Context context,String apkPath) {
    if (TextUtils.isEmpty(apkPath)){
        Toast.makeText(context,"更新失败!未找到安装包", Toast.LENGTH_SHORT).show();
        return;
    }

    File apkFile = new File(apkPath
            + apkCacheName);

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setDataAndType(
            Uri.fromFile(apkFile),
            "application/vnd.android.package-archive");
    context.startActivity(intent); 
}

问题出在启动安装程序阶段
由于没升级 7.0 系统之前都没有问题,于是就在 Android 官网查看了一下 Android 7.0 新特性,终于发现其中 “在应用间共享文件” 一栏明确指出了这个问题

 

 

解决问题

官方给出的解决方式是通过 FileProvider 来为所共享的文件 Uri 添加临时权限,详细请看这里

  • 在 <application> 标签下添加 FileProvider 节点
<application
   ...>
   ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.skyrin.bingo.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
   ...
</application>

android:authority 属性指定要用于 FileProvider 生成的 content URI 的 URI 权限,这里推荐使用 包名.fileprovider 以确保其唯一性。

<provider> 的 <meta-data> 子元素指向一个 XML 文件,用于指定要共享的目录。

  • 在 res/xml 目录下创建文件 file_paths.xml 内容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-cache-path path="app/" name="apk"/>
</paths>

<external-cache-path> 表示应用程序内部存储目录下的 cache/ 目录,完整路径为 Android/data/com.xxx.xxx/cache/

path 属性用于指定子目录。

name 属性告诉 FileProvider 为 Android/data/com.xxx.xxx/cache/app/ 创建一个名为 apk 的路径字段。

想要通过 FileProvider 为文件生成 content URI 只能在此处指定目录,以上示例就表示我将要共享 Android/data/com.xxx.xxx/cache/app/ 这个目录,除此之外还可以共享其它目录,对应的路径如下:

标签路径
<files-path name="name" path="path" /> Context.getFilesDir()
<cache-path name="name" path="path" /> getCacheDir()
<external-path name="name" path="path" /> Environment.getExternalStorageDirectory()
<external-files-path name="name" path="path" /> Context.getExternalFilesDir()
<external-cache-path name="name" path="path" /> Context.getExternalCacheDir()
  • 完成以步骤后,我们修改出问题的代码如下:
/**
 * 安装apk
 */
public static void installApk(Context context,String apkPath) {
    if (TextUtils.isEmpty(apkPath)){
        Toast.makeText(context,"更新失败!未找到安装包", Toast.LENGTH_SHORT).show();
        return;
    }

    File apkFile = new File(apkPath
            + apkCacheName);

    Intent intent = new Intent(Intent.ACTION_VIEW);
    //Android 7.0 系统共享文件需要通过 FileProvider 添加临时权限,否则系统会抛出 FileUriExposedException .
    if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri contentUri = FileProvider.getUriForFile(context,"com.skyrin.bingo.fileprovider",apkFile);
        intent.setDataAndType(contentUri,"application/vnd.android.package-archive");
    }else {
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(
                Uri.fromFile(apkFile),
                "application/vnd.android.package-archive");
    }
    context.startActivity(intent);
}
...
//调用,apkPath 入参就是 xml 中共享的路径
String apkPath = context.getExternalCacheDir().getPath()+ File.separator+"app"+File.separator;
AppUpdate.installApk(context,apkPath );

结语

除了上面这个问题,在 Android 7.0 之前开发的分享图文、浏览编辑本地图片、共享互传文件等功能如果没有使用 FileProvider 来生成 URI 的话,在 Android 7.0 上就必须做这种适配了,所以平时建议大家多关注 Android 新的 API ,尽早替换已被官方废弃的 API ,实际上 FileProvider 在 API Level 22 已经添加了。

源码

此demo已经上传到GitHub,如有需要自行下载
GitHub:
https://github.com/huangshuyuan/UpdateDemo/

我的博客:
http://blog.csdn.net/Imshuyuan/article/details/62886741

我的简书:
http://www.jianshu.com/p/2ab0459a9c3c


 

Android应用程序的自动更新升级(自身升级、通过tomcat)

置顶 快上车_来不及解释了 2012-01-16 13:13:28 27151 收藏 11
分类专栏: Andriod 文章标签: android tomcat json string web服务 网络
版权

Andriod
专栏收录该内容
18 篇文章3 订阅
订阅专栏

刚入手android一个多月,因公司需要提交技术文档,才写了这个demo测试,想保存下来,以备后用!有什么不对的地方欢迎大家指正,这个示例也是参考了网上别人的demo自己做的。

Android应用程序的升级(自身升级)

一、 引言:

很多的Android应用都具有版本检测和自动更新的功能,用户一键就可以完成软件的升级和更新。Android应用程序的升级本质上是利用了Linux系统的软件包管理和安装机制,而对于上层这一功能的开发来说很容易,只需要我们开发人员利用Android自带的API就可以实现。

二、 功能说明:

1、本示例用来实现单个应用程序的自身升级

2、程序启动时,连接tomcat7 web服务器进行版本的检测,若有新版本则提示更新

3、将从web服务器下载的新版本的APK文件放到sdcard中

4、监听新版本的APK应用是否安装完成,如果是,则将下载的apk文件从sdcard中删除

三、 程序框架流程:

 

 

四、 环境说明:

1、 服务器端:Ubuntu下的tomcat7web服务器,安装后默认端口是8080,Android模拟器访问时要将apk文件放到 /var/lib/tomcat7/webapps/ROOT/目录下,Android模拟器的访问方式是http://10.0.2.2/NewAppSample.apk

2、 Android模拟器端的开发环境:

Ubuntu+eclipse+ADT

五、 流程详解及关键点说明:

(一) 新版本的应用程序(NewAppSample)准备:

a) 新建一个android工程,编辑其版本代码为2,高于我们的旧版本用于更新测试,版本名称为1.0.1


b) 编辑应用程序对应的版本信息文件version.json


说明:后缀为json的文件是一种轻量级的数据交换格式,比xml要快很多,适合于小型数据的网络交换,其实质类似键值对,键用字符串的形式表示与其值用冒号隔开,能存储多种数据类型。

(二) 旧版本的应用程序准备:

1、在其AndroidManifest.xml中定义版本代码为versionCode=”1”让其自动生成即可,我们主要利用程序的版本代码的高低来判断是否有新的版本,用于更新。

2、我们在应用程序启动时自动联网检测是否有新的版本,即在onCreate()函数中进行联网检测。

a) 从服务器获得读取版本信息文件version.json,我们单独写了一个类来实现,用其GetUpdateInfo静态方法来返回读取的version.json,返回形式是字符串。代码如下


b) 获得当前旧的应用程序版本信息,我们单独封装了一个类CurrentVersion,用其中的静态方法来获得当前应用的版本信息,包括程序的名称版本,代码版本,和应用程序名字。

代码如下:


c) 将从服务器version.json获得的字符串解析出我们需要的版本信息


d) 进行代码版本的比较,提示是否更新当前的应用。


(三) 显示更新提示框

 

 

(四) 下载新的APK文件

 

下载完成时要将进度条对话框取消并进行是否安装新应用的提示


(五) 安装新的应用:

 

Intent的setDataAndType的一个参数是应用程序的绝对路径(在sdcard中),第二个参数是文件对应的MIME类型,android系统中的APK文件默认为application/vnd.android.package-archive,该文件的MIME类型在tomcat服务器中的/var/lib/tomcat7/conf文件中有对应。

(六) 网络检测代码和sdcard中APK文件的删除

 


关键说明:若不用广播接收的方式,直接在安装后的代码中实现删除下载的APK文件的话,会出现还没安装完成就把APK文件删除了的情况。在进入安装新的APK文件时会进入系统的提示进行一步一步的安装操作,所以我们无法判断应用程序什么时候完全安装完成。我们用监听(应用程序安装或替换的)广播的方式来实现,当接受到应用程序有ADDED或则REPLACED的广播时我们再执行APK文件的删除操作。

六、 Demo效果图例:

1.提示更新


2.下载新版本的应用


3.提示是否安装


4.进入系统安装提示


5.正在安装


6.安装完成


7.打开新版本的应用


七、 完成过程中出现的问题以及关键点说明:

1. Android模拟器连接tomcat7服务器下载时访问地址IP不能用localhost,因为android模拟器把localhost当成自己了,应该用10.0.2.2测试

2. 下载的APK文件和版本信息的json文件应该放在/var/lib/tomcat7/webapps/ROOT/目录下不然无法访问到。

3. JSON文件的解析方式参考JSON附文理解。

4. 示例中涉及到的权限:

a) 与sdcard相关的权限:示例中我们需要在sdcard中创建和删除文件的权限和sdcard的读写权限。

 

b) 与网络相关的权限:示例中我们需要访问网络的权限和获得网络状态的权限(测试网络是否可用),示例中我们只测试了网络是否可用,我们还可以添加网络是否已经连接的进一步判断。


5. 监听应用程序是否安装完成

在工程的Manifest.xml文件中添加要接受的广播action,这里我们监听应用程序本身的替换和系统中应用程序的添加两个action,应用程序的替换监听好像只能监听自身被替换,这一点待考察。


源码下载地址:本篇源码下载
八、 JSON附文:
JSON的定义:
一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据交换。JSON采用兼容性很高的文本格式,同时也具备类似于C语言体系的行为。

为什么用JSON?
很简单,因为它比xml快十倍。

有哪些应用案例?
Twitter、豆瓣、facebook等公司的开放api,一般这些服务都会提供多种格式供开发人员选择(xml、json、atom等),而在手机终端上,我们自然希望给用户最佳体验,所以我选用最有效率的json格式。

JSON的结构:
Name/ValuePairs 类似所熟知的Keyedlist、Hash table、Disctionary和Associative array。在Android平台中同时存在另外一个类“Bundle”,某种程度上具有相似的行为。

org.json.JSONObject Array,一组有序的数据列表。

Android中 JSON相关的类(4个)和Exceptions(1个):
l JSONArray

l JSONObject

l JSONStringer

l JSONTokener

l JSONException

 

JSONObject:
这是系统中有关JSON定义的基本单元,其包含一对儿(Key/Value)数值。它对外部(External:应用toString()方法输出的数值)调用的响应体现为一个标准的字符串(例如:{"JSON": "Hello, World"},最外被大括号包裹,其中的Key和Value被冒号":"分隔)。其对于内部(Internal)行为的操作格式略微,例如:初始化一个JSONObject实例,引用内部的put()方法添加数值:newJSONObject().put("JSON", "Hello, World!"),在Key和Value之间是以逗号","分隔。

Value的类型包括:Boolean、JSONArray、JSONObject、Number、String或者默认值JSONObject.NULL object。

有两个不同的取值方法:

get(): 在确定数值存在的条件下使用,否则当无法检索到相关Key时,将会抛出一个Exception信息。

opt(): 这个方法相对比较灵活,当无法获取所指定数值时,将会返回一个默认数值,并不会抛出异常。

JSONArray:
它代表一组有序的数值。将其转换为String输出(toString)所表现的形式是用方括号包裹,数值以逗号”,”分隔(例如:[value1,value2,value3],大家可以亲自利用简短的代码更加直观的了解其格式)。这个类的内部同样具有查询行为,get()和opt()两种方法都可以通过index索引返回指定的数值,put()方法用来添加或者替换数值。

同样这个类的value类型可以包括:Boolean、JSONArray、JSONObject、Number、String或者默认值JSONObject.NULL object。

JSONStringer:
根据官方的解释,这个类可以帮助快速和便捷的创建JSON text。其最大的优点在于可以减少由于格式的错误导致程序异常,引用这个类可以自动严格按照JSON语法规则(syntaxrules)创建JSON text。每个JSONStringer实体只能对应创建一个JSON text。

根据下边的实例来了解其它相关信息:

string myString= new JSONStringer().object()

.key("AR").value("www.Androidres.com!")

.endObject()

.toString();

结果是一组标准格式的JSON text:{”AR”:”www.Androidres.com!”}

其中的.object()和.endObject()必须同时使用,是为了按照Object标准给数值添加边界。同样,针对数组也有一组标准的方法来生成边界.array()和.endArray()。

JSONTokener:
这个是系统为JSONObject和JSONArray构造器解析JSON source string的类,它可以从source string中提取数值信息。

JSONException:
是JSON.org类抛出的异常信息。
————————————————


 

Android软件自动更新升级(自动下载安装新版本)

na2609613672 2019-04-25 11:24:12 10942 收藏 24
分类专栏: android
版权

android
专栏收录该内容
59 篇文章1 订阅
订阅专栏
因为Android系统版本的不同踩了不少坑,在此记录。

1.Android 8.0以后无法下载到SDK中
问题原因: 文件的存储权限原因导致的
动态申请sdk存储权限:Android 6.0以后都是需要动态申请权限的,注意即使动态申请了权限也要在AndroidManifest.xml申请一下,因为需要兼容低版本,低版本中没有动态申请权限一说。
Android 7.0以后又对存储权限加了限制,应用私有目录被限制访问:
私有文件的文件权限不应再由所有者放宽,使用MODE_WORLD_READABLE/MODE_WORLD_WRITEABLE将抛出异常
向应用外传递file://URI会出发FileUriExposedException
解决方法:
问题一:判断版本,动态申请权限
//----------------------------------------动态申请权限-------------------------------
private void sdkPermission() {
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.M) {//高于6.0版本,动态申请权限
if (ContextCompat.checkSelfPermission(context, "android.permission.WRITE_EXTERNAL_STORAGE") != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}, 111);
} else {
downloadApk();
}
}
else {
downloadApk();//低于6.0版本,直接下载apk
}
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 111:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//权限已经都通过了,可以下载apk到SDk中了
downloadApk();
} else {
// 没有申请权限
showPermissionDialog();
}
break;
default:
}
}
问题二:7.0以后使用FileProvider访问sdk私有文件
FileProvider:
当targetSdkVersion>=24时,会存在上述问题,可能涉及到的场景有:拍照,程序安装等。
同时,官方在v4包(api=22开始)中引入FileProvider类用于程序间私有文件的共享。该类继承自ContentProvider,使用时需 要在清单文件中注册。

第一步 注册:在清单文件中通过标签注册,参考代码对属性进行说明:

注意:当自定义类继承FileProvider时,需要更改name属性值为该类的相对路径。

<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
...
</application>
</manifest>
说明:meta_data中的

name:为固定值android.support.FILE_PROVIDER_PATHS

resource:所对应的xml文件路径

第二步 resource对应的xml文件编写:作用设置可访问的共享文件
FileProvide只能对在paths中声明了的文件夹下的文件生成uri。

下例子就是声明私有文件目录下images/下的文件可以临时访问(文件在res/xml/目录下),下面时一个简单的样式:

1.创建xml:res/xml/file_paths

 

2.file_paths.xml中添加代码:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
<files-path name="my_docs" path="docs/"/>
</paths>
</resources>
因为的子标签可以有多种,这里对所有进行说明:

 

子标签中属性说明:

 

第三步 FileProvider的使用:
1、通过路径生成要分享的File对象。

2、使用FileProvider生成临时访问uri (FileProvide.getUriForFile()).

3、客户端可以使用uri通过ContentResolver.openFileDescriptor获取到一个ParcelFileDescriptor

案例:

File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

//改代码生成的uri为:content://com.mydomain.fileprovider/my_images/default_image.jpg
临时权限的授予方式

使用Context.grantUriPermission(package,Uri,mode_flags)方法,使用想要的模式。这个方法通过mode_flags方法授予客户 端package的临时权限,有两个取值,FLAG_GRANT_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSOIN。该方式允许后,可通过revokeUriPermission终止,或者手机重启后
通过Intent
通过Intent的setData()方法将该uri放入Intent中
可以为Intent设置flag,设置一个或两个, FLAG_GRANT_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSOIN
将Intent发送给其他app,大部分情况,通过setResult()来做 这种方法获取的权限,当接收的Activity在栈中一直活跃时都会保留,当activity栈finish时,权限会自动移除。被允许的activity所在的app的其他组件也会被允许该权限。
案例代码:
步骤一:在AndroidManifest.xml中的application注册FileProvider的清单文件
//临时访问文件的注册
<provider
android:authorities="${applicationId}.android7.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
></provider>
步骤二:res中创建xml包,添加file_paths.xml,在file_paths.xml中添加如下文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path path="Android/data/com.example.socket.phonesafe/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
</resources>
步骤三:代码中使用
private void installApk(File file) {
Uri uri=null;
try {
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//为intent 设置特殊的标志,会覆盖 intent 已经设置的所有标志。
if(Build.VERSION.SDK_INT>=24){//7.0 以上版本利用FileProvider进行访问私有文件
uri=FileProvider.getUriForFile(content,content.getPackageName() + ".android7.fileprovider",file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//为intent 添加特殊的标志,不会覆盖,只会追加。
}
else {
//直接访问文件
uri=Uri.fromFile(file);
intent.setAction(Intent.ACTION_VIEW);
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}

2.下载成功了没有跳出应用安装界面
原因:
Android 8.0的系统中,“未知来源应用权限”的开关被移除掉了,取而代之的是未知来源应用的管理列表,如果你想要安装某个被自己所信任的开发者的app,则需要在每一次都手动授权“安装未知应用”的许可。设置页面如下图:(在华为Android 8.0中,打开该设置页面:设置列表—>安全与隐私—>更多安全设置—>安装未知应用)

 

如图所示*,若某个应用选择的是“不允许”,那么假设app手动升级的时候,就无法成功跳转到安装页面进行正常的App升级流程了,此时需要手动去授权才行,但是很多用户并不知道需要这么设置。

解决方法:
安装软件前,先监测是否许可了此软件的“安装未知应用”,如果没有允许就跳转到设置页面,然后用户手动允许一下,如果允许了,就可以直接安装应用了。

 

步骤一:在AndroidManifest.xml文件中,添加REQUEST_INSTALL_PACKAGES权限

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
步骤二:判断版本号、是否允许安装此未知应用

if(Build.VERSION.SDK_INT>Build.VERSION_CODES.O){
installAllowed=content.getPackageManager().canRequestPackageInstalls();//是否允许安装包
if(installAllowed){
installApk(file);//允许,安装
}else {
//跳转到设置页面,设置成允许安装
Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + content.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
content.startActivity(intent);
installApk(file);
return;
}

}
//版本低于8.0
else {
installApk(file);
}
//安装apk
private void installApk(File file) {
Uri uri=null;
try {
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//为intent 设置特殊的标志,会覆盖 intent 已经设置的所有标志。
if(Build.VERSION.SDK_INT>=24){//7.0 以上版本利用FileProvider进行访问私有文件
uri=FileProvider.getUriForFile(content,content.getPackageName() + ".android7.fileprovider",file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//为intent 添加特殊的标志,不会覆盖,只会追加。
}
else {
//直接访问文件
uri=Uri.fromFile(file);
intent.setAction(Intent.ACTION_VIEW);
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
百度网盘源码:https://pan.baidu.com/s/1gKly1Syobmj1MkainAwOkw

密码:9lj5

注解:版本信息和要升级的apk存放在自己tomcat服务器中的webapps—ROOT中,然后开启tomcat,代码中就可以直接访问下载了。

 

 

参考:https://blog.csdn.net/chen_white/article/details/72819814

https://www.jianshu.com/p/e05f35fbb569

 

注:如果你的gradle版本和我的不一样导致下载的代码不能运行,可看我另一篇代码可以解决这个问题https://blog.csdn.net/na2609613672/article/details/89086952