Android 实战-版本更新(okhttp3、service、notification)

2023-05-16

转发请注明出处:http://www.jianshu.com/p/b669940c9f3e

前言

整理功能,把这块拿出来单独做个demo,好和大家分享交流一下。
版本更新这个功能一般 app 都有实现,而用户获取新版本一般来源有两种:
- 一种是各种应用市场的新版本提醒
- 一种是打开app时拉取版本信息
- (还要一种推送形式,热修复或打补丁包时用得多点)

这两区别就在于,市场的不能强制更新、不够及时、粘度低、单调。

摘要

下面介绍这个章节,你将会学习或复习到一些技术:

- dialog 实现 key 的重写,在弹窗后用户不能通过点击虚拟后退键关闭窗口
- 忽略后不再提示,下个版本更新再弹窗
- 自定义 service 来承载下载功能
- okhttp3 下载文件到 sdcard,文件权限判断
- 绑定服务,实现回调下载进度
- 简易的 mvp 架构
- 下载完毕自动安装

<点分期>版本更新.png

这个是我们公司的项目,有版本更新时的截图。当然,我们要实现的demo不会写这么复杂的ui。

功能点(先把demo的最终效果给上看一眼)

UpdateDemo.gif

dialog

dialog.setCanceledOnTouchOutside() 触摸窗口边界以外是否关闭窗口,设置 false 即不关闭
dialog.setOnKeyListener() 设置KeyEvent的回调监听方法。如果事件分发到dialog的话,这个事件将被触发,一般是在窗口显示时,触碰屏幕的事件先分发到给它,但默认情况下不处理直接返回false,也就是继续分发给父级处理。如果只是拦截返回键就只需要这样写

mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                return keyCode == KeyEvent.KEYCODE_BACK && 
                              mDialog != null && mDialog.isShowing();
            }
        });

忽略

忽略本次版本更新,不再弹窗提示
下次有新版本时,再继续弹窗提醒

其实这样的逻辑很好理解,并没有什么特别的代码。比较坑的是,这里往往需要每次请求接口才能判断到你app是否已经是最新版本。

这里我并没有做网络请求,只是模拟一下得到的版本号,然后做一下常规的逻辑判断,在我们项目中,获取版本号只能通过请求接口来得到,也就是说每次启动请求更新的接口,也就显得非常浪费,我是建议把这个版本号的在你们的首页和其它接口信息一起返回,然后写入在 SharedPreferences。每次先判断与忽略的版本是否一样,一样则跳过,否则下次启动时请求更新接口

public void checkUpdate(String local) {
        //假设获取得到最新版本
        //一般还要和忽略的版本做比对。。这里就不累赘了
        String version = "2.0";
        String ignore = SpUtils.getInstance().getString("ignore");
        if (!ignore.equals(version) && !ignore.equals(local)) {
            view.showUpdate(version);
        }
    }

自定义service

这里需要和 service 通讯,我们自定义一个绑定的服务,需要重写几个比较关键的方法,分别是 onBind(返回和服务通讯的频道IBinder)、unbindService(解除绑定时销毁资源)、和自己写一个 Binder 用于通讯时返回可获取service对象。进行其它操作。

context.bindService(context,conn,flags)
- context 上下文
- conn(ServiceConnnetion),实现了这个接口之后会让你实现两个方法onServiceConnected(ComponentName, IBinder) 也就是通讯连通后返回我们将要操作的那个 IBinder 对象、onServiceDisconnected(ComponentName) 断开通讯
- flags 服务绑定类型,它提供很多种类型,但常用的也就这里我我们用到的是 Service.BIND_AUTO_CREATE, 源码对它的描述大概意思是说,在你确保绑定此服务,就自动启动服务。(意思就是说,你bindService之后,传的不是这个参数,有可能你的服务就没反应咯)

通过获取这个对象就可以对 service 进行操作了。这个自定义service篇幅比较长,建议下载demo下来仔细阅读一番.

public class DownloadService extends Service {

    //定义notify的id,避免与其它的notification的处理冲突
    private static final int NOTIFY_ID = 0;
    private static final String CHANNEL = "update";

    private DownloadBinder binder = new DownloadBinder();
    private NotificationManager mNotificationManager;
    private NotificationCompat.Builder mBuilder;
    private DownloadCallback callback;

    //定义个更新速率,避免更新通知栏过于频繁导致卡顿
    private float rate = .0f;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        mNotificationManager.cancelAll();
        mNotificationManager = null;
        mBuilder = null;
    }

    /**
     * 和activity通讯的binder
     */
    public class DownloadBinder extends Binder{
        public DownloadService getService(){
            return DownloadService.this;
        }
    }

    /**
     * 创建通知栏
     */
    private void setNotification() {
        if (mNotificationManager == null)
            mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mBuilder = new NotificationCompat.Builder(this,CHANNEL);
        mBuilder.setContentTitle("开始下载")
                .setContentText("正在连接服务器")
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setOngoing(true)
                .setAutoCancel(true)
                .setWhen(System.currentTimeMillis());
        mNotificationManager.notify(NOTIFY_ID, mBuilder.build());
    }

    /**
     * 下载完成
     */
    private void complete(String msg) {
        if (mBuilder != null) {
            mBuilder.setContentTitle("新版本").setContentText(msg);
            Notification notification = mBuilder.build();
            notification.flags = Notification.FLAG_AUTO_CANCEL;
            mNotificationManager.notify(NOTIFY_ID, notification);
        }
        stopSelf();
    }

    /**
     * 开始下载apk
     */
    public void downApk(String url,DownloadCallback callback) {
        this.callback = callback;
        if (TextUtils.isEmpty(url)) {
            complete("下载路径错误");
            return;
        }
        setNotification();
        handler.sendEmptyMessage(0);
        Request request = new Request.Builder().url(url).build();
        new OkHttpClient().newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Message message = Message.obtain();
                message.what = 1;
                message.obj = e.getMessage();
                handler.sendMessage(message);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.body() == null) {
                    Message message = Message.obtain();
                    message.what = 1;
                    message.obj = "下载错误";
                    handler.sendMessage(message);
                    return;
                }
                InputStream is = null;
                byte[] buff = new byte[2048];
                int len;
                FileOutputStream fos = null;
                try {
                    is = response.body().byteStream();
                    long total = response.body().contentLength();
                    File file = createFile();
                    fos = new FileOutputStream(file);
                    long sum = 0;
                    while ((len = is.read(buff)) != -1) {
                        fos.write(buff,0,len);
                        sum+=len;
                        int progress = (int) (sum * 1.0f / total * 100);
                        if (rate != progress) {
                            Message message = Message.obtain();
                            message.what = 2;
                            message.obj = progress;
                            handler.sendMessage(message);
                            rate = progress;
                        }
                    }
                    fos.flush();
                    Message message = Message.obtain();
                    message.what = 3;
                    message.obj = file.getAbsoluteFile();
                    handler.sendMessage(message);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (is != null)
                            is.close();
                        if (fos != null)
                            fos.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }


    /**
     * 路径为根目录
     * 创建文件名称为 updateDemo.apk
     */
    private File createFile() {
        String root = Environment.getExternalStorageDirectory().getPath();
        File file = new File(root,"updateDemo.apk");
        if (file.exists())
            file.delete();
        try {
            file.createNewFile();
            return file;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null ;
    }

    /**
     * 把处理结果放回ui线程
     */
    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    callback.onPrepare();
                    break;

                case 1:
                    mNotificationManager.cancel(NOTIFY_ID);
                    callback.onFail((String) msg.obj);
                    stopSelf();
                    break;

                case 2:{
                    int progress = (int) msg.obj;
                    callback.onProgress(progress);
                    mBuilder.setContentTitle("正在下载:新版本...")
                            .setContentText(String.format(Locale.CHINESE,"%d%%",progress))
                            .setProgress(100,progress,false)
                            .setWhen(System.currentTimeMillis());
                    Notification notification = mBuilder.build();
                    notification.flags = Notification.FLAG_AUTO_CANCEL;
                    mNotificationManager.notify(NOTIFY_ID,notification);}
                    break;

                case 3:{
                    callback.onComplete((File) msg.obj);
                    //app运行在界面,直接安装
                    //否则运行在后台则通知形式告知完成
                    if (onFront()) {
                        mNotificationManager.cancel(NOTIFY_ID);
                    } else {
                        Intent intent = installIntent((String) msg.obj);
                        PendingIntent pIntent = PendingIntent.getActivity(getApplicationContext()
                        ,0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                        mBuilder.setContentIntent(pIntent)
                                .setContentTitle(getPackageName())
                                .setContentText("下载完成,点击安装")
                                .setProgress(0,0,false)
                                .setDefaults(Notification.DEFAULT_ALL);
                        Notification notification = mBuilder.build();
                        notification.flags = Notification.FLAG_AUTO_CANCEL;
                        mNotificationManager.notify(NOTIFY_ID,notification);
                    }
                    stopSelf();}
                    break;
            }
            return false;
        }
    });


    /**
     * 是否运行在用户前面
     */
    private boolean onFront() {
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
        if (appProcesses == null || appProcesses.isEmpty())
            return false;

        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.processName.equals(getPackageName()) &&
                    appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                return true;
            }
        }
        return false;
    }


    /**
     * 安装
     * 7.0 以上记得配置 fileProvider
     */
    private Intent installIntent(String path){
        try {
            File file = new File(path);
            String authority = getApplicationContext().getPackageName() + ".fileProvider";
            Uri fileUri = FileProvider.getUriForFile(getApplicationContext(), authority, file);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
            } else {
                intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
            }
            return intent;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 销毁时清空一下对notify对象的持有
     */
    @Override
    public void onDestroy() {
        mNotificationManager = null;
        super.onDestroy();
    }


    /**
     * 定义一下回调方法
     */
    public interface DownloadCallback{
        void onPrepare();
        void onProgress(int progress);
        void onComplete(File file);
        void onFail(String msg);
    }
}

okhttp3下载文件

看通透事情的本质,你就可以为所欲为了。怎么发起一个 okhttp3 最简单的请求,看下面!简洁明了吧,这里抽离出来分析一下,最主要还是大家的业务、框架、需求都不一样,所以节省时间看明白写入逻辑就好了,这样移植到自己项目的时候不至于无从下手。明白之后再结合比较流行常用的如 Retrofit、Volley之类的插入这段就好了。避免引入过多的第三方库而导致编译速度变慢,项目臃肿嘛。

我们来看回上面的代码,看到 downApk 方法,我是先判断路径是否为空,为空就在通知栏提示用户下载路径错误了,这样感觉比较友好。判断后就创建一个 request 并执行这个请求。很容易就理解了,我们要下载apk,只需要一个 url 就足够了是吧(这个url一般在检测版本更新接口时后台返回)。然后第一步就配置好了,接下来是处理怎么把文件流写出到 sdcard。

写入:是指读取文件射进你app内(InputStream InputStreamReader FileInputStream BufferedInputStream
写出:是指你app很无赖的拉出到sdcard(OutputStream OutputStreamWriter FileOutputStream BufferedOutputStream

仅此送给一直对 input、ouput 记忆混乱的同学

 Request request = new Request.Builder().url(url).build();
 new OkHttpClient().newCall(request).enqueue(new Callback() {});

写出文件

    InputStream is = null;
    byte[] buff = new byte[2048];
    int len;
    FileOutputStream fos = null;
    try {
           is = response.body().byteStream();                        //读取网络文件流
           long total = response.body().contentLength();             //获取文件流的总字节数
           File file = createFile();                                 //自己的createFile() 在指定路径创建一个空文件并返回
           fos = new FileOutputStream(file);                         //消化了上厕所准备了
           long sum = 0;
           while ((len = is.read(buff)) != -1) {                     //嘣~嘣~一点一点的往 sdcard &#$%@$%#%$
              fos.write(buff,0,len);
              sum+=len;
              int progress = (int) (sum * 1.0f / total * 100);
              if (rate != progress) {
                   //用handler回调通知下载进度的
                   rate = progress;
              }
            }
            fos.flush();
            //用handler回调通知下载完成
     } catch (Exception e) {
            e.printStackTrace();
     } finally {
            try {
                   if (is != null)
                       is.close();
                   if (fos != null)
                       fos.close();
             } catch (Exception e) {
                   e.printStackTrace();
             }
      }

文件下载回调

在上面的okhttp下载处理中,我注释标注了回调的位置,因为下载线程不再UI线程中,大家需要通过handler把数据先放回我们能操作UI的线程中再返回会比较合理,在外面实现了该回调的时候就可以直接处理数据。

 private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case 0://下载操作之前的预备操作,如检测网络是否wifi
                    callback.onPrepare();
                    break;

                case 1://下载失败,清空通知栏,并销毁服务自己
                    mNotificationManager.cancel(NOTIFY_ID);
                    callback.onFail((String) msg.obj);
                    stopSelf();
                    break;

                case 2:{//回显通知栏的实时进度
                    int progress = (int) msg.obj;
                    callback.onProgress(progress);
                    mBuilder.setContentTitle("正在下载:新版本...")
                            .setContentText(String.format(Locale.CHINESE,"%d%%",progress))
                            .setProgress(100,progress,false)
                            .setWhen(System.currentTimeMillis());
                    Notification notification = mBuilder.build();
                    notification.flags = Notification.FLAG_AUTO_CANCEL;
                    mNotificationManager.notify(NOTIFY_ID,notification);}
                    break;

                case 3:{//下载成功,用户在界面则直接安装,否则叮一声通知栏提醒,点击通知栏跳转到安装界面
                    callback.onComplete((File) msg.obj);
                    if (onFront()) {
                        mNotificationManager.cancel(NOTIFY_ID);
                    } else {
                        Intent intent = installIntent((String) msg.obj);
                        PendingIntent pIntent = PendingIntent.getActivity(getApplicationContext()
                        ,0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                        mBuilder.setContentIntent(pIntent)
                                .setContentTitle(getPackageName())
                                .setContentText("下载完成,点击安装")
                                .setProgress(0,0,false)
                                .setDefaults(Notification.DEFAULT_ALL);
                        Notification notification = mBuilder.build();
                        notification.flags = Notification.FLAG_AUTO_CANCEL;
                        mNotificationManager.notify(NOTIFY_ID,notification);
                    }
                    stopSelf();}
                    break;
            }
            return false;
        }
    });

自动安装

android 随着版本迭代的速度越来越快,有一些api已经被遗弃了甚至不存在了。7.0 的文件权限变得尤为严格,所以之前的一些代码在高一点的系统可能导致崩溃,比如下面的,如果不做版本判断,在7.0的手机就会抛出FileUriExposedException异常,说app不能访问你的app以外的资源。官方文档建议的做法,是用FileProvider来实现文件共享。也就是说在你项目的src/res新建个xml文件夹再自定义一个文件,并在配置清单里面配置一下这个
fileProvider.png

AndroidMainfest.png

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external"
        path=""/>
</paths>

安装apk

 try {
            String authority = getApplicationContext().getPackageName() + ".fileProvider";
            Uri fileUri = FileProvider.getUriForFile(this, authority, file);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            //7.0以上需要添加临时读取权限
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
            } else {
                Uri uri = Uri.fromFile(file);
                intent.setDataAndType(uri, "application/vnd.android.package-archive");
            }

            startActivity(intent);

            //弹出安装窗口把原程序关闭。
            //避免安装完毕点击打开时没反应
            killProcess(android.os.Process.myPid());
        } catch (Exception e) {
            e.printStackTrace();
        }

已把 Demo 放在github
希望大家能从中学习到东西,不再困惑

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 实战-版本更新(okhttp3、service、notification) 的相关文章

  • 【算法学习】有效括号 valid-parentheses (Java 参考)

    题目描述 给定一个只包括 xff0c xff0c xff0c xff0c xff0c 的字符串 xff0c 判断字符串是否有效 有效字符串需满足 xff1a 左括号必须用相同类型的右括号闭合 左括号必须以正确的顺序闭合 注意空字符串可被认为
  • 【算法学习】约瑟夫环(含公式思想总结)(Java)

    目录 一 题目描述二 思路分析思路1 xff1a 模拟思路2 xff1a 数学方法递推公式 xff1a 推导思路why 为什么可以倒推how 如何倒推 三 参考代码四 测试连接 一 题目描述 这个问题是以弗拉维奥 约瑟夫命名的 xff0c
  • Ubuntu下matplotlib中文乱码

    原因 xff1a 由于matplotlib的默认安装字体不支持中文格式 解决思路 xff1a 将默认字体换成可以支持中文字体包 matplotlib默认的字体文件为 anaconda3 lib python3 7 site packages
  • 【算法学习】n个骰子的点数(Java)

    目录 一 题目描述二 思路分析核心公式公式推导依据 三 参考代码四 测试连接 一 题目描述 把n个骰子扔在地上 xff0c 所有骰子朝上一面的点数之和为s 输入n xff0c 打印出s的所有可能的值出现的概率 你需要用一个浮点数数组返回答案
  • 自动复制粘贴“机器人”(go)

    背景 最近遇到个问题 xff0c 需要将 html 批量转换为 markdown xff0c 尝试过很多转换库结果并不理想 xff0c 发现通过复制粘贴的方式效果十分不错 xff08 mac xff0c 从chrome浏览器 xff0c 复
  • Go Error 错误处理总结

    Go Error 一 设计理念 简单考虑成功而不是只有成功没有隐藏的控制流完全交给调用者控制 errorError are values xff08 Rob Pike xff09 二 错误与异常 go 中的错误处理主要使用到error 和
  • Linux创建新用户和key登陆

    1 root用户身份登陆 2 新增一个用户 useradd m new user name 3 切换至新用户 su new user name 4 生成公钥和私钥 ssh keygen t rsa 一路回车 5 cd ssh 6 cat i
  • 详解C++中的ANSI与Unicode和UTF8三种字符编码基本原理与相互转换

    目录 1 概述 2 Visual Studio中的字符编码 3 ANSI窄字节编码 4 Unicode宽字节编码 5 UTF8编码 6 如何使用字符编码 7 三种字符编码之间的相互转换 xff08 附源码 xff09 7 1 ANSI编码与
  • 让ubuntu16.04开机进入命令行模式

    让ubuntu16 04开机进入命令行模式 使用Ubuntu时 xff0c 有时候我们不想开机进入桌面 xff0c 想直接进入命令行 xff0c 这样启动的比较快 xff0c 1 首先我们修改grub文件 xff0c 改为如图所示 xff1
  • centos7 开启关闭服务

    centos 7 中使用systemctl工具来管理服务程序 xff0c 包括了service和chkconfig 启动一个服务 xff1a systemctl start firewalld service 关闭一个服务 xff1a sy
  • 基于SPWM的逆变器程序应用及自制电路

    自制逆变器的电路及程序应用 设计并制作 一个简易逆变器 xff0c 其结构如图所示 逆变器进行负载试验时 xff0c 需在其输出端接负载 通常情况下 xff0c 输出电能消耗在该负载上 2 基本要求 逆变器输出端仅连接电阻性负载 xff0c
  • DNS:Bind安全配置、视图

    Bind中的安全配置 Bind支持ACL xff08 访问控制列表 xff09 功能 xff1a 主要实现把一个或多个地址归并为一个集合 xff0c 并通过一个统一的名称调用 格式 xff1a acl acl name ip ip net
  • Mozilla 修复跨平台加密库 NSS 中的严重漏洞

    聚焦源代码安全 xff0c 网罗国内外最新资讯 xff01 编译 xff1a 代码卫士 Mozilla 修复了影响跨平台网络安全服务 NSS 加密库中一个严重的内存损坏漏洞 CVE 2021 43527 NSS 可用于开发启用安全功能的客户
  • Ubuntu下将anaconda打包移植到另一个台Ubuntu下,使用ananconda的离线包库,安装包

    由于某种需求 xff0c 需要将annaconda移植到另外一台Ubuntu下 xff0c 而且另一台无法联网 xff0c 而且需要安装一些包 xff0c 使用wheel和setup安装失败后使用的这种方法 思路 xff1a 将本地的ann
  • IT66121 720P@60配置文件

    这里为IT66121 720P 64 60的配置文件 xff0c 在Linux下我是使用i2cset工具先验证再写驱动的 xff0c 所以这里我也简单记录一下 IT66121分为两个bank xff0c 一个是bank0的配置 xff0c
  • CCS编译报错 error #10234-D: unresolved symbols remain

    最近刚刚接触CCS编译器 xff0c 然后自己搭建的工程中出现了这个报错 xff1a 检查了头文件以及库都是正常的 xff0c 但就不清楚是什么原因 xff0c 最后同事帮忙解决了这个问题 xff0c 如下解决 xff1a 工程右键选择Pr
  • AM4379 关于CCS下无法正常加载程序

    最近调试4379的时候发现了个问题 xff0c 这里记录下 将boot set设置为全1模式下 xff0c 然后加载程序 xff0c 发现加载是可以正常加载 xff0c 但是运行图标是灰色的 xff08 如图 xff09 xff0c 然后我
  • S5P6818 卸载驱动时报错

    调试6818的时候 xff0c 在卸载驱动时候报了这样的错误 xff1a rmmod can 39 t change directory to 39 3 4 39 39 No such file or directory 解决办法 xff1
  • S5P6818 移植phytool报错

    下载地址 xff1a GitHub wkz phytool Linux MDIO register access 编译 xff1a make CC 61 home tronlong 6818 arm 2009q3 bin arm none
  • Ubuntu16.04 卸载vmware

    1 先查看安装的虚拟机 vmware installer l 然后会显示版本和产品名称 Product Name Product Version 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61

随机推荐

  • HPS是什么?包括哪些内容?

    文章来自 xff1a http bbs eeworld com cn thread 454766 1 1 html 1 HPS Hard processor system 字面意思就是硬件处理器系统 xff0c 应该指的是和arm核相连的硬
  • Ubuntu16.04 段错误(核心已转储)的解决办法

    由于软件需求我写了个测试软件 xff0c 我定义两个u8 的buf为1280 720 8的大小 xff0c 也差不多要接近于15M 的大小了 xff0c 在语法没有错误的情况下直接报段错误 xff08 核心已转储 xff09 的错误 查找了
  • WebDriver使用指南(完整篇)

    第1章 入门 1 1 下载selenium2 0的lib包 http code google com p selenium downloads list 官方UserGuide xff1a http seleniumhq org docs
  • 关于Virtualbox使用win7鼠标卡顿问题

    工作中要用到虚拟机 xff0c 自己装了一个win7系统 xff0c 结果发现鼠标卡顿的不行 xff0c 不能流畅运行 xff0c 于是查找了很多文章 xff0c 终于解决了问题 软件版本 xff1a virtualbox7 0 解决方案
  • Ubuntu挂载分区

    1 查看本地分区 xff0c 找到你想要挂载的分区名称 sudo fdisk l 图中为以整块2T硬盘 xff0c 分为5 6 7 8 9区 xff0c 以 dev sdb7 为例 xff0c 想要将其挂载到 home plan other
  • 允许远程该计算机的其他用户

    当你想进入服务器的其他用户时 xff0c 发现通过administrator是无法直接切换用户的 xff0c 那么我们应该怎么设置呢 xff1f 解决方案 xff1a 1 右击计算机 gt 属性 gt 高级系统设置 gt 远程 xff0c
  • 学习Linux 编程的几本好书

    这次涉及到了具体的平台 GNU Linux Linux下开发与明显不同于Windows平台的特点 xff0c 从开发工具到项目组织 xff0c 都有较大的差距 首先声明 xff0c 在做Linux平台开发之前 xff0c 首先要熟练使用Li
  • IE8报错:Unable to modify the parent container element before the child element is closed

    xfeff xfeff 转自 xff1a http blog csdn net xinwang article details 9786447 IE8中会报 HTML Parsing Error Unable to modify the p
  • APNs 访问不到的问题

    APNs会将链接太频繁的链接视为DDos攻击 xff0c 所以链接频率不要太高 目前每5分钟连接接一次 因为使用了加密链接 xff0c 会被GFW随机阻断 看脸 看有的说建议用国外VPS 单个ip连接每次发送消息数量不要超过1000条 xf
  • 谷歌原生DocumentUI文件浏览的原理

    相信多数想了解谷歌DocumentUI设计思想的码农都会遇到障碍 xff0c 文件浏览究竟是怎么实现的 xff0c 进入DocumentUI的UI层 xff0c 不难发现 xff0c 我们是通过查询数据库获取cursor xff0c 但是查
  • linux下快速查找文件

    版权声明 xff1a 本文为博主xxt 测试开发之路的原创文章 xff0c 遵循 CC 4 0 BY SA 版权协议 xff0c 转载请附上原文出处链接和本声明 本文链接 xff1a https blog csdn net xxmonsto
  • Android 多语言对照表

    语言地区文件夹名称南非荷兰语南非values af rNA南非荷兰语纳米比亚values af rZA阿肯语加纳values ak rGH阿姆哈拉语埃塞俄比亚values am rET阿拉伯语阿拉伯联合酋长国values ar rAE阿拉伯
  • android图片轮播+点击跳转广告页面

    Android轮播网络图片 43 点击跳转广告页面 一些新手总是很头疼怎么获取网络图片的url之后让它像一些广告那样轮播起来 xff0c 点击图片之后跳转到指定网页 效果如下 在布局引用自定义控件 span class hljs pi lt
  • 用SurfaceView实现级联分层图(粗略篇)

    先看效果图 实际运行很流畅 xff0c 运行内存1M左右 最近脑抽 xff0c 想实现一个亲戚关系图谱的应用 xff0c 但始终没有找到合适的开源控件 xff0c 于是就看到一篇 利用递归算法和堆栈实现android思维导图大纲图的动态绘制
  • Android各种访问权限Permission详解

    在Android的设计中 xff0c 资源的访问或者网络连接 xff0c 要得到这些服务都需要声明其访问权限 xff0c 否则将无法正常工作 在Android中这样的权限有很多种 xff0c 这里将各类访问权限一一罗列出来 xff0c 供大
  • 桌面远程连接Ubuntu图形界面和开启ssh连接

    1 xff0c 设置Ubuntu为可被远程连接 Settings Sharing Screen Sharing Access Options设置一个远程连接的密码 连接远程是的密码 xff0c 区别于用户密码 2 xff0c 安装支持vnc
  • Android动态设置Shape

    有过一些开发经验的朋友 xff0c 在做圆角按钮的背景时可能不再需要 9的切图了 xff0c 而一般都是在drawable文件夹下面建立一个xml文件shape 其他状态不变色 或者selector 按下 选中状态变色 xff0c 但是如果
  • Android 禁止锁屏或黑屏

    转载请注明出处 xff1a http blog csdn net snailbaby soko article details 56842467 场景 xff1a 通常情况我们使用的 app 都不需要用到这个功能 但一些平板的开发就很常见了
  • Android 美团Robust热更新 使用入门

    转载请注明出处 http blog csdn net snailbaby soko article details 69524380 本篇文章已授权微信公众号 guolin blog xff08 郭霖 xff09 独家发布 Android热
  • Android 实战-版本更新(okhttp3、service、notification)

    转发请注明出处 http www jianshu com p b669940c9f3e 前言 整理功能 xff0c 把这块拿出来单独做个demo xff0c 好和大家分享交流一下 版本更新这个功能一般 app 都有实现 xff0c 而用户获