Android性能最佳实践

最近看了谷歌官方关于Android性能最佳实践的部分,感觉应该要记下来才行。这里面有很多不看不知道的东西,我也为一些比较难懂的点增加了一些额外阅读的链接。刚总结完了JNI的小贴士,往后几天会陆续在这篇文章中把性能最佳实践这一部分补充完整。 后面还有安全最佳实践,权限最佳实践等部分,每一部分一篇文章,都会分开记录下来。 性能小贴士 这里介绍了一些小优化,可以提升app的整体性能。但是不一定会带来性能的飙升。选择正确的算法和数据结构是获得良好性能的首要任务。这里的小优化都是一些通用的编码实践,实现高效代码。 实现高效代码有两个基本规则: 不要重复造轮子 不要无谓分配内存 不要创建不必要的对象 创建对象总是要付出代价的。当我们创建越来越多的对象的时候,我们其实在强制地让垃圾回收器更加频繁地工作。这会造成类似于『打嗝』一样的波动,影响用户体验。 因此,要避免任何不必要的对象创建。以下是一些建议: 比如说,如果一个方法要返回String对象。Java内部实现的时候,String最后都是会被加到StringBuffer中的,因此不需要在代码中创建一个临时的StringBuffer或者StringBuilder对象来append字符串,直接用String就好了。 从字符串中截取子字符串的时候,不要创建新的String对象去存放原字符串的拷贝,可以选择直接return substring就好。 更加影响性能的一些点在于数组的使用上: int类型的array比Integer类型的array性能更好;同时,想方设法避免(int, int)这样多维数组的使用,将多维数组降成一维从而获取性能提升; 总结一下,尽量避免临时对象的创建,这样能减少垃圾回收器的运行次数,提升用户体验。 尽可能使用static而不是virtual 如果一个方法不需要访问这个类的成员,那么用static修饰这个方法。这样一来,大致上会有15%-20%的访问速度提升。 这里stackoverflow有个针对于这个问题的很好的解释 使用static final修饰常量 使用statis final修饰常量,能提高访问速度。 这里涉及到java的编译原理,做不了深入解释了。 要注意的是,这只对原始类型数据以及String常量有效。 避免在类的内部使用Getter/Setter方法 在C++等语言中,(i = getCount())这样的代码是很好的编码习惯,编译器会提升执行效率。 但是在安卓中,使用这样的方式是很糟糕的想法。在安卓中调用Virtual Method差不多就像寻找成员变量一样消耗性能。在类中,直接访问成员变量而不要使用Getter方法。 没有JIT编译的情况下,直接访问成员的速度将3倍快于调用Getter方法。拥有JIT编译的情况下,直接访问成员变量变得和访问局部变量一样快捷,将达到7倍快于调用Getter方法。 这是一片关于Java中Virtual Method的文章 使用增强for循环 除了ArrayList的遍历之外,增强for循环的性能是最好的。无论有没有JIT编译支持,在ArrayList的遍历上使用手写for循环,都将3倍快于增强for。 但是在其他容器上,增强for的性能在没有JIT的情况下快于手写for循环,在有JIT的情况下等同于手写for循环。 鉴于更少的代码,在除了ArrayList的地方,都使用增强for。 看下面的情形: zoo()方法是最慢的,使用了手写for循环,并且每次循环都要获取array长度; one()方法快一些,不用每次都获取array长度了; two()方法最快,使用了增强for(这里不是ArrayList)。 记住在非ArrayList的情况下用增强for。 使用默认包访问权限而不是私有private权限 考虑如下情形: 内部类Inner访问了外部类的成员变量mValue和成员方法run,语法上是没有问题的,运行结果也正确。 但是,因为mValue和run方法被private修饰,VM是禁止直接访问一个类的私有成员的,因为Foo 和Inner是两个不同的类。 为了能使内部类访问外部类的私有成员,编译器生成了如下两个方法: 因此,无论是访问mValue还是run,都调用了其中一个方法。 在前面讲到了,在同一个类中要访问成员或者方法最好的方式是直接访问,而不是通过方法调用。因此,这里就是性能损耗的地方。 最好将private去掉,也就是使用默认权限。但是这样一来其他同一包中的类就能访问这些成员了。 最好的办法就是不要写这样的代码了。 避免使用浮点…

Android设计与实现-卷1-JNI框架基础

看完了『Android设计与实现』第一部分,做了笔记,但是发现还少一张图能把JNI层的方法声明和调用关系明确表示出来的。花了点时间把Log系统的方法声明和调用关系整理出来了,看着舒服多了,也有条理。 红色两块是Log系统的Java文件和对应的C++文件。所有的方法以及调用关系从这两个矩形开始。android_util_Log.java包含本地方法声明,android_util_Log.cpp包含了本地方法的实现、Java本地方法和C++方法实现之间的关系映射以及将这种映射关系告知Dalvik虚拟机的过程。

自制Android RSS阅读器

这个问题困扰我挺久了,android端找不到一款好的RSS阅读器。我就想简简单单打开自己订阅的RSS每天看一看,没有注册,没有广告,就简简单单的。终于抽了两天时间出来写了个BETA。 所有代码都放在我的Github上了。 记录一下开发过程中遇到的一些问题。 1. RecyclerView处在ConstraintLayout中出现的视图问题 视图的问题如图。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TwCD6atd-1587400775279)(http://img.blog.csdn.net/20170602222804432?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGVpc2VqaXVodWNoZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)] 在adapter的onCreateViewHolder()方法中,就是按照 这样来写的。子视图的高度也都是确定的。如果这么简单就好了。问题照旧。 尝试了很久,结果症结出在ConstraintLayout和RecyclerView一起使用上。 上图中是一个fragment,有问题的fragment的根布局如下: 因为其他很多地方我都排查过了,所以对这个布局心生怀疑。 我就把ConstraintLayout改成了FrameLayout,修改后的布局如下: 问题解决。 2. 对RSS的XML文档的解析 读取RSS中的内容其实就是解析一下XML文档。 RSS的格式有个大概的标准,大致就是<channel>或者<rss>开头,然后是最外层的<title>, <link>, <description>的内容;紧接着就是<item>,代表每个子条目;每个<item>标签中也有相应的<title>, <link>, <description>节点。 比较了feed.yeeyan.org/select,zhihu.com/rss,http://www.read.org.cn/feed以及feed.androidauthority.com四个RSS,发现有三个问题: <link>标签的内容,可能包含在<link>的属性中,如:<link href=”http://…”> <description>标签有时候又叫<subtitle> <item>标签有时候又叫<entry> 因此在解析的时候,将这三个问题考虑进去,大部分的RSS应该都能解析了。 简单的应用,使用sqlite作为本地存储,如果重复添加已有的RSS,就相当于刷新RSS的内容。 目前还只有简单的添加RSS,浏览其内容的功能。 陆续还会抽空把刷新,进度条和 RSS补全库等功能加上。 代码希望对大家有用。有需要改进的地方,可以邮件我或者ISSUE。 上个截图:

关于Android Studio导入工程卡在Building Gradle上

有时候,我从github上拽下来一个工程,然后在导入的时候,就卡在Building Gradle那一步,进度条一直在读,但是就是没有反应,等急了要取消还取消不掉,必须强制退出。如下图。 我猜测是因为工程的gradle版本和本地的不一致,导致android studio要去下载相应版本的gradle,但是国外的网站真的是太慢了… 解决办法如下。 查看本地Gradle版本 Mac或者Linux用户,如果android studio正确安装的话,应该在home目录下有一个.gradle文件夹。 cd ~/.gradle/wrapper/dists 执行上面的命令查看本地gradle版本,如图。 修改目标工程的gradle/wrapper/gradle-wrapper.properties 在文件管理器中找到目标工程的gradle/wrapper/gradle-wrapper.properties文件,打开并修改版本号为本地任一gradle版本。 再次导入工程 再次导入工程,整个过程就很顺畅了。

Tensorflow-1-Tensorflow Moblie Android平台编译安装

之前就看到Tensorflow有手机平台的API了,今天终于抽了点时间出来鼓捣一下。 首先是把tensorflow克隆到本地一份。 既然是谷歌官方要求的,最好把–recurse-submodules加上,文档说可以避免一些数据结构序列化时的编译问题。 这是android demo的github主页。 准备编译 1.安装bazel bazel是谷歌自己的构建工具。tensorflow只能部分支持cmake或者gradle,而bazel是tensorflow工程的主要构建工具。 点这里下载Bazel。 Mac和Linux用户根据文档进行安装。Windows用户,按照官方建议到下面的链接下载demo的二apk文件,目前bazel在windows平台还处于试验阶段。 Windows用户点这里直接下载apk bazel安装成功与否,用bazel version检查版本即可。 2.下载NDK 点这里下载最新版本NDK。 最好下载r12b版本的,最新的r13b可能与bazel有兼容问题。 下载完成后解压到自定义目录,然后在~/.bash_profile(linux在~/.bashrc)下添加环境变量。环境变量的添加过程大家百度一下吧,不是这里的重点。 3.下载>=23 Android SDK Tensorflow Android Demo必须在大于等于23的API环境中编译。可以打开Android Studio中的SDK Manager来安装最新的SDK。 4. 编辑Tensorflow根目录下的WORKSPACE文件 回到tensorflow根目录,(当前在android目录就往上两级)。打开WORKSPACE文件。 在文件开头部分找到 这两部分定义了SDK和NDK的路径,把/path/to/your的部分改成系统相应的路径。然后将每一行前的注释去掉。如下: 开始编译 在tansorflow根目录执行, 进行编译,变异过程如下: 一切顺利的话,编译成功,如下图: 安装APK DEMO 变异成功之后bazel会在bazel-bin目录下面生成apk文件。用数据线连上手机,执行 即可安装到手机。 DEMO截图 TF Classify 图片分类的Demo。可以看到tf识别出了台式电脑和显示器。 TF Stylize 这个Demo很好玩,不只是将一张图片的风格渲染到摄像头,还能通过调节来综合两张图片的风格。 点击左上角的数字按钮,可以从128一直选到720。刚开始以为这个数值跟训练过程中的神经元数量有关。后来想想这些模型应该都是训练好了的,移动平台还没有能力进行这样的训练。看源码得知这个就是最终呈现的图片的尺寸,越高图片越清晰。但是相应的,对手机性能的要求就越高。我在魅族pro 6 plus上测试,选择256之后,就开始卡顿的厉害,720的话,定在一个点上大概5-7秒才能看到渲染之后的图像。 总结 市面上已经有很多运用深度学习的应用的例子。但是大多数都是只能让用户使用已经训练好的模型,而无法让用户自定义。比如我想用我自己喜欢的两张图片作为风格,来渲染视频,而不是图库中已有的。受限于移动平台自身的计算能力,目前还做不到;而把计算放到云端,用户体验又太差(prizma网络不好的时候要等很久,还可能失败…)。 期待用量子计算机来做深度计算哈哈哈~

AndroidStudio导入库文件

今来看看Android Studio怎么使用第三方库~网上找了很多教程貌似都有问题,自己尝试了一下,简单易行,亲测有效~ 第一步:准备好Android Studio格式的库文件 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUeL3azw-1587400315304)(http://img.blog.csdn.net/20150916073333627)] 这是一个SlidingMenu的库文件,点击这里下载SlidingMenu;大家可以下载来做测试~如果是Eclipse出身的库,还不知道该怎么办,以后遇到再说吧~ 第二步:在项目内点击File -> New -> Import Module导入模块 然后选择第三方库的根目录 如果已经包含这个模块,会出现感叹号后面的提示,第一次肯定没有啦~ 这时,切换到Project视图,就能看到库文件已经导入了~ 第三步:修改库文件的build.gradle文件 这个是自己应用的build.gradle文件内容 我们要保证compoleSdkVersion,buildToolsVersion,minSdkVersion,targetSdkVersion在app和导入的库文件中都一致~ 下面是修改之后的库文件里的build.gradle~ 第四步:在Project视图下找到工程的build.gradle文件,替换库文件的classpath 将文件中的classpath ‘com.android.tools.build:gradle:1.3.0’一行,复制到库文件的gradle文件中,替换相应的classpath行,保证一致~ 第五步:这个不是通用步骤(可忽略) SlidingMenu中使用的是android.util.FloatMath类,导入的时候会报错;原因是这个类已经过时了,建议使用java.lang.Math类~只需要找到相应的错误,将FloatMath替换成Math类即可~ 第六步:设置项目依赖关系 以添加Module dependency的形式,关联库文件;然后Clean一下Project~ SlidingMenu就等着你折腾了~ 这样看来,Android Studio中导入第三方库的过程应该都大同小异吧~

Android事件处理机制轻量级源码分析

正确理解Android中的事件分发和拦截机制,对于在多个ViewGroup和View嵌套以及自定义控件的时候能够正常处理用户行为至关重要。 如果要把源码一行一行看,研究事件处理机制就要到天荒地老了。这是为什么这篇博客叫轻量级源码分析,简单易懂。屏蔽掉了冗长的无需太过关心的源码,把握一下几行重点,其实事件处理就可以有一定的了解了。这篇博文就是根据源码一步一步走了一下事件处理的流程,分模块来看应该思路应该会比较清晰一点~ 事件分发和拦截101 开始之前首先明确一下,在源码当中,理解事件分发,关注一下dispatchTouchEvent()方法;理解事件拦截,再多关注一个onInterceptTouchEvent()方法即可~ 还有噢,dispatchTouchEvent()方法返回true表示响应全部事件,而返回false则表示只响应第一个事件,忽略后续的事件~onInterceptTouchEvent()方法返回true表示拦截事件,子View不会接收到该事件;返回false表示不拦截事件,向子View传递该事件,让子View尝试处理~ 哟西~ Let`s hacking… 一、View的事件处理机制 View中的事件分发对于本身可以被点击和不可被点击的View有些许不同~ 1. View的事件分发之不可被点击的View 先看一个小示例哟~这一小段代码中有两个控件,一个ImageView,一个Button,这两个控件最终都是继承View的,就用这两个控件来阐述一下View的事件处理机制 这一小段代码,还是粘上来吧~ 先说一说本身不可被点击的ImageView~这里给imageView添加一个onTouchListener,我们看看在return false的情况下,单击一次,打印了几次输出信息~ 运行之后发现输出了imageView eventAction = 0。由于一次单击包括按下和抬起两个操作(0表示按下的操作,1表示抬起的操作),因此从这个现象可以看出,抬起的操作在这个情况下没有被监测到。想知道为什么,就来看看源码咯~ ImageView是继承View的(继承了dispatchTouchEvent()方法),所以应该到View的源码中查看一下dispatchTouchEvent()(我们要关注的就是这个方法嘛)方法,看看View是怎么进行事件分发的(就知道ImageView是怎么处理事件的了)。过滤了不必要的代码之后,应该关注的代码如下: dispatchTouchEvent()方法 View的dispatchTouchEvent()方法中,这个if语句的意思是说,如果mOnTouchListener 引用不为空,并且当前要接收事件的控件(此处就是这个IamgeView)处于可用状态(只要不设置setEnable = false,这个条件是始终成立的,所以后面就不说这个条件了),并且mOnTouchListener这个引用的onTouch方法返回了true,那么整个dispatchTouchEvent()就返回true,否则返回onTouchEvent(event)方法执行的结果。 那么ImageView被点击之后,是否执行了if语句呢?下面来判断一下if中的条件该ImageView是否满足~ 所以呢,此时的ImageView是不会执行dispatchTouchEvent()方法中的if语句块的,而会执行其所继承的onTouchEvent(event)方法,并返回相应的布尔结果。 到这里,还无法得知为什么ImageView上的抬起事件没有被监测到。那就来看看这个onTouchEvent(event)方法到底干了什么。这个方法代码超长的,哈哈,轻量级源码如下: onTouchEvent()方法 这里又有一个if语句块,这个if说,如果你这个接收事件的控件可以被点击,或者可以被长点击,那么就执行if中的逻辑,然后返回true,否则整个方法返回false。那就来看看我们的ImageView是否满足条件咯~ 然后再回到dispatchTouchEvent()方法,既然此时的ImageView不能执行if中的代码,而onTouchEvent()方法又返回了false,所以整个dispatchTouchEvent()就返回了false。根据文章开头所说dispatchTouchEvent()返回true和false的意义得知,这个单击事件整体只有按下会被处理,所以就只会打印一次输出信息咯~ 如果想让ImageView同时响应按下和抬起的事件,该怎么办呢?有两个方式: 2. View的事件分发之可以被点击的View 接下来就说说Button这个东西~ 在同样的环境下,同样地为Button添加了一个触摸事件监听器,在onTouch()方法中返回了false;运行之后,输出了Button eventAction = 0和Button eventAction = 1。这里同样返回false,为什么就能打印两次信息,为什么Button的整个单击事件可以被完全监测呢? 原因就在于Button本身是可以被点击的;在其他条件都相同的情况下,Button本身可以被点击的属性,可以让其执行onTouchEvent()方法中的if语句 onTouchEvent()方法 从而返回true;进而dispatchTouchEvent()方法返回true,单击事件的按下和抬起都会被监测~ 3. onClick()、onTouch()和onTouchEvent的调用顺序 上面一点很好理解。那进一步,如果也给Button再来一个setOnClickListener()呢?就像这样 在onTouch()方法返回false的情况下,还会打印”button onclick”的信息吗? 运行之后发现,”button onclick”的信息在Button eventAction =…

Android Studio最新版本(1.3)JNI开发流程总结

JNI应该是Android开发人员的标配了~从Eclipse转到Android Studio有好多东西就必须重新学习。网上已有的对于Android Studio JNI开发的博文貌似都有些问题,在最新版本的Studio一些步骤已经无法使用。花了些时间重新整理了一下整个JNI开发的流程。环境:Android Studio 1.3.2;MackBook Pro 第一步:如果还没有在Studio中下载NDK的话,打开项目结构,然后点击Android NDK location一栏下面的Download NDK,开始下载NDK(需要翻墙) 下载完成之后,Studio自动帮我们安装;之后会自动将项目默认结构设置中的NDK位置(Close Project -> Configure -> Project Defaults -> Project Structure -> Android NDK location)设置到当前NDK的安装位置。 第二步:配置完NDK环境,就可以开始写本地方法了。先在MainActivity中添加一个本地方法 第三步:点击Build -> Make Project,生成项目的字节码文件(至此,这些步骤跟已有的没有什么区别) 第四步:生成头文件这一步有些不同。其他博文上要求在Terminal中切换到main目录,然后输入javah -d jni ….. <包名.类名>一大堆命令,但是发现这个命令无法使用,会有如下报错 这里说没有制定classes文件,所以就试试用其他方法生成头文件。在控制台(在项目根目录)输入: 这样,在java目录下,会生成一个.h头文件 第五步:在main文件夹下,新建一个jni文件夹,然后将刚生成的头文件拷贝到该文件夹中 第六步:创建C代码,这个例子就是完成点击按钮,调用本地方法,然后输出C代码中的一个字符串到控制台 第七步:在jni文件夹加入Android.mk文件和Application.mk文件 第八步:在MainActivity中加载模块 第九步:/app/build.gradle的defaultConfig中加入ndk配置;moduleName的值就是Android.mk中LOCAL_MODULE的值 如果是通过第一步Studio自动安装的NDK的话,那么项目的local.properties中会自动加上ndk的路径;如果没有,请务必手动添加 如果这个时候编译整个工程,会报出 这样的错误;这里只要根据提示,在gradle.properties中加入”android.useDeprecatedNdk=true”即可 然后重新编译,即可成功部署。

Android Studio导入aidl

用到aidl了,把这点小经验写上来分享一下~过程很简单的咯~看图~ 第一步:切换到Project视图(这样更容易操作一点) 第二步:在main文件夹上右键创建一个新的aidl文件夹 第三步:右键aidl文件夹创建aidl文件所需的包名 注意保证包名和文件的包名一致~ 第四步:将aidl文件拷入对应包中 第五步:Rebuild工程 工程要Rebuild一下,才能正常使用aidl文件~

Android Studio从SVN检出代码

ADT已经不受宠爱了, 安卓开发者要速速转向谷歌的亲儿子Android Studio. 这篇文章讨论一下如何使用Android Studio从SVN检出代码. PS: 前提是svn中现存代码是基于Android Studio的. 如何导入Eclipse代码, 请参阅 Eclipse项目的导入 开始吧~~ 第一步: 在欢迎页面选择 Check out project from Version Control (如果已经开启了一个项目, 选择File –> Close Project关闭就能看到欢迎页面啦~) 第二步: 选择Subversion 第三步: 选择SVN仓库 (如果还没有SVN仓库, 点击绿色的+添加服务器SVN仓库的地址即可) 选择仓库之后, 点击Checkout 第四步: 选择检出到的目的地文件夹 放哪就随便大家咯~ 选择完目的地文件夹, 点击OK 第五步: 选择导出文件夹位置 Destination一栏, 务必选择带有项目名称的选项; 如果不带项目名称文件夹检出, 就会把项目文件夹下的子文件夹丢到检出目录中, 这样项目一多就不是事儿啦~ Depth中选择infinity, 这个选项就是原先Eclipse当中Recursive的意思 其他选项使用默认就好, 然后点击OK 第六步: 选择JDK版本 根据个人情况进行选择~ 电脑上是1.8的版本, 就选择1.8咯~ 选择完毕点击OK…