记录一下纠结了两天的问题。
项目中遇到如下错误:
Error:Execution failed for task ':mobile:packageAllDebugClassesForMultiDex'.
> java.util.zip.ZipException: duplicate entry:android/support/annotation/AnyRes.class
这个问题是因为项目中引入了 support-annotations
包,但是导入的其他 jar 包中包含了这个package,重复导致了 duplicate entry
错误。
在 app 的 build.gradle
中加入:
android{
...
configurations {
all*.exclude group: 'com.android.support', module: 'support-annotations'
}
}
项目中尽量只导入一个库,在引用其他库时注意查看该库的依赖版本,在导入 support v4 包或者其他 v7 包时尽量考虑只在主项目中导入一次,在任何 library 中减少使用。
这个问题的由来,应该也是项目历史原因,在我接手时,项目 build.gradle
中 buildTypes 是这样的:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
}
这里竟然 debug 和 release 使用了同样的 minifyEnabled 配置,默认情况下 minifyEnabled 应该是 false,设置成 true 之后,debug 下无法调试,无法设置断点。查看 git 历史这个问题竟然从项目初始就这样,真无法想象他们是怎么调试的。然后将 minifyEnabled 设置成 false 之后就产生了 multiDex 错误,然后是上面的错误。
buildTypes 下 minifyEnabled
选项, 设置为 true 情况下, debug 无法设置断点及调试1, 但是这个选线在 release 下非常有用,可以减少包的大小,缩减无用代码(shrink unless codes)。
修改后的配置:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
debug {
minifyEnabled false
debuggable true
}
}
multiDex 的错误倒是还好解决2,设置:
android {
compileSdkVersion 22
buildToolsVersion "23.0.0"
defaultConfig {
minSdkVersion 14 //lower than 14 doesn't support multidex
targetSdkVersion 22
// Enabling multidex support.
multiDexEnabled true
}
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
然后在 Application 类中设置:
public class YouApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
正是这个错误促使我看了 Gradle 中依赖的语法,总结如下:
Gradle 项目可能依赖其他组件,包括外部的二进制文件,或者另一个 Gradle 项目。
下面代码可以添加本地 libs
目录下所有 jar 包:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
android {
...
}
如果想要配置本地 jar 包,可以依照下面顺序:
compile files('libs/xxx.jar')
在 build.gradle
文件中,或者 compile fileTree(dir: 'libs', include: '*.jar')
Gradle 支持从远端 Maven 或者 Ivy repositories 自动拉取依赖。首先远端 repository 需要加入列表,其次需要定义具体依赖。
repositories {
jcenter()
}
dependencies {
compile 'com.google.guava:guava:18.0'
}
android {
...
}
Note:
jcenter()
是远端库的URL缩写, Gradle 支持本地和远程库。通常用子目录来配置 libraries ,例如
MyProject/
+ app/
+ libraries/
+ lib1/
+ lib2/
三个项目, Gradle 通过如下指定:
:app
:libraries:lib1
:libraries:lib2
每一个项目都有自己的 build.gradle
文件,另外在跟目录下有 settings.gradle
来描述工程:
MyProject/
| settings.gradle
+ app/
| build.gradle
+ libraries/
+ lib1/
| build.gradle
+ lib2/
| build.gradle
settings.gradle
中的描述:
include ':app', ':libraries:lib1', ':libraries:lib2'
然后在 :app
项目中定义,依赖 library :
dependencies {
compile project(':libraries:lib1')
}
可以使用如下的语法排除 v4 包:
compile ('com.android.support:recyclerview-v7:+') {
exclude module: 'support-v4'
}
同样也可以在:
android{
configurations {
'com.android.support', module: 'support-v4'
}
}
下排除。
更加系统的学习 Android Gradle build system 可以参考官方文档,或者中文译本3。
关于 build.gradle
文件中的字段属性,可以参考 Android Plugin DSL Reference.
http://stackoverflow.com/questions/31926189/android-debugging-with-minifyenabled-true ↩
StackOverflow 和 官网 ↩
一些常用的 adb 命令,包括Android录屏,及从电脑复制文件,从 Android 设备拉取文件等等。
adb 的全称是 Android Debug Bridge, 这个命令可以用来发送一系列指令给 Android 设备,包括但不限于基本的 Linux 指令。只要手机或者任何 Android 设备开启了Debug模式并且取得信任,adb 命令几乎可以用来做任何事情。因此网络上也存在使用 adb 来入侵同一局域网下的 Android 盒子的例子。
来查看设备是否已经连接
$ adb devices
List of devices attached
2dd11c6e device
adb kill-server
将文件复制到 Android 设备上:
adb push filename.txt /sdcard/Download/
将文件从 Android 设备上拉回本地
adb pull /sdcard/Download/filename.txt ~/filename.txt
重启设备,在刷机时经常使用
重启进去 bootloader
或者使用 adb reboot recovery
直接进入 recovery 模式
直接使用该命令可以进入手机的 Shell.
进入 Android Shell 之后就可以使用任何 Linux 命令来直接操作 Android 设备, 比如:
adb shell chmod 666 /data/filename.txt
shell 中可以直接截取设备的屏幕
adb shell screencap -p /sdcard/screen.png
adb pull /sdcard/screen.png
adb shell rm /sdcard/screen.png
使用 screencap
截图保存到 sdcard 上,使用 pull
命令拉到本地并删除sd卡中文件。这种方式比较繁杂,需要三个步骤,如果查看 screencap -h
会发现,帮助中有一行,如果不加文件名,命令会将结果输出到标准输出。那么
adb shell screencap -p > screen.png
adb shell screencap -p | sed 's/\r$//' > screen.png
直接将结果输出到本地,之所以使用 sed
是因为需要将多余的 \r
删除。adb shell
在执行时会将 \n
转换为 \r\n
。
在本地添加 alias
alias and-screencap="adb shell screencap -p | sed 's/\r$//'"
and-screencap > screen.png
在 shell 命令中可以使用 screenrecord
命令来录制屏幕。需要 Android 4.4 (API Level 19)及以上,该命令将屏幕保存成 MPEG-4 文件。不录制声音。
adb shell screenrecord /sdcard/Download/filename.mp4
使用 Ctrl + c 来停止录像,否则默认录制 3min 或者使用 --time-limit
参数来指定。
注意:
screenrecord
命令可以按照设备原始分辨率录屏,但是有些设备可能不支持原始分辨率,此时降低分辨率再尝试。可选参数:
--help
--size <width*Height>
比如 1280*720
.--bit-rate <rate>
默认码率 4Mbps,6Mbps 可以设置 6000000.--time-limit <TIME>
默认为180 (3min) 设置时间,单位秒--rotate
旋转输出--verbose
显示 log 信息,如果不设置,不显示任何信息使用如下命令1:
adb shell 'pm list packages'
使用如下命令去除前面的 package:
adb shell pm list packages | awk -F ":" '{print $2}'
# 或者,-f 用来输出第二部分, -d 用来标示分割符号
adb shell pm list packages | cut -f 2 -d ":"
在使用 adb shell 进入 手机 Shell 之后可以使用, pm help 来获取更多关于 pm 命令的详情。关于 pm 的命令。
adb shell pm list packages
adb shell pm list packages -f
See their associated file.adb shell pm list packages -d
Filter to only show disabled packages.adb shell pm list packages -e
Filter to only show enabled packages.adb shell pm list packages -s
Filter to only show system packages.adb shell pm list packages -3
Filter to only show third party packages.adb shell pm list packages -i
See the installer for the packages.adb shell pm list packages -u
Also include uninstalled packages.adb shell pm list packages --user <USER_ID>
The user space to query.命令格式
adb shell input text <string>
adb shell input keyevent <key code number or name>
adb shell input tap <x> <y>
adb shell input swipe <x1> <y1> <x2> <y2> [duration(ms)]
模拟按键的,keycode 为 3 时表示 HOME 键,更多的可以参考后文的附录
adb shell input keyevent 3
模拟点击时,后面接的 x,y 都是真实屏幕分辨率,比如想要点击屏幕(x,y)=(150,150)像素的位置
adb shell input tap 150 150
模拟屏幕滑动和tap是一样的,只是需要给出滑动的起点和终点两个坐标值
adb shell input swipe 150 150 200 200
可以使用该命令安装应用程序,命令的通用模式:
adb install app.apk
可以使用 -r
命令更新应用
adb install -r apkfilename.apk
adb install
的其他参数
adb install -l app.apk
forward lock applicationadb install -r app.apk
替换存在的应用adb install -t app.apk
允许测试包adb install -s app.apk
在sdcard上安装adb install -d app.apk
允许比现在安装版本更低的包 allow version code downgradeadb install -p app.apk
增量更新 partial application install同理可以使用 adb uninstall apkfilename.apk
来卸载应用。
使用 adb uninstall -k apkfilename.apk
可以卸载应用,但是保留数据。
通过网络来使用 adb,可以通过该命令来连接网络上开放远程调试的设备。
adb connect <host>[:<port>]
远程连接之后就可以使用上面的所有命令,也可卸载远程设备上的应用,也可以安装本地的apk到远程设备上,也可以通过adb命令来控制远程设备上的应用。
通过下面的命令向远程设备安装应用
adb -s <ip:port> install -r <app.apk>
向远程设备发送按键事件,比如下面向远程设备发送 Powerbutton 按键按下事件
adb -s <ip:port> shell input keyevent 26
或者启动远程设备上的应用 Start the App
adb -s <ip:port> shell monkey -p <package name> -c android.intent.category.LAUNCHER 1
在电视盒子上安装 Youtube 应用的时候,遇到了几个版本,突然想到这个命令
adb shell dumpsys package com.google.android.youtube | grep version
可以用来查看当前这个 package 的版本号。
KEYCODE_UNKNOWN=0;
KEYCODE_SOFT_LEFT=1;
KEYCODE_SOFT_RIGHT=2;
KEYCODE_HOME=3;
KEYCODE_BACK=4;
KEYCODE_CALL=5;
KEYCODE_ENDCALL=6;
KEYCODE_0=7;
KEYCODE_1=8;
KEYCODE_2=9;
KEYCODE_3=10;
KEYCODE_4=11;
KEYCODE_5=12;
KEYCODE_6=13;
KEYCODE_7=14;
KEYCODE_8=15;
KEYCODE_9=16;
KEYCODE_STAR=17;
KEYCODE_POUND=18;
KEYCODE_DPAD_UP=19;
KEYCODE_DPAD_DOWN=20;
KEYCODE_DPAD_LEFT=21;
KEYCODE_DPAD_RIGHT=22;
KEYCODE_DPAD_CENTER=23;
KEYCODE_VOLUME_UP=24;
KEYCODE_VOLUME_DOWN=25;
KEYCODE_POWER=26;
KEYCODE_CAMERA=27;
KEYCODE_CLEAR=28;
KEYCODE_A=29;
KEYCODE_B=30;
KEYCODE_C=31;
KEYCODE_D=32;
KEYCODE_E=33;
KEYCODE_F=34;
KEYCODE_G=35;
KEYCODE_H=36;
KEYCODE_I=37;
KEYCODE_J=38;
KEYCODE_K=39;
KEYCODE_L=40;
KEYCODE_M=41;
KEYCODE_N=42;
KEYCODE_O=43;
KEYCODE_P=44;
KEYCODE_Q=45;
KEYCODE_R=46;
KEYCODE_S=47;
KEYCODE_T=48;
KEYCODE_U=49;
KEYCODE_V=50;
KEYCODE_W=51;
KEYCODE_X=52;
KEYCODE_Y=53;
KEYCODE_Z=54;
KEYCODE_COMMA=55;
KEYCODE_PERIOD=56;
KEYCODE_ALT_LEFT=57;
KEYCODE_ALT_RIGHT=58;
KEYCODE_SHIFT_LEFT=59;
KEYCODE_SHIFT_RIGHT=60;
KEYCODE_TAB=61;
KEYCODE_SPACE=62;
KEYCODE_SYM=63;
KEYCODE_EXPLORER=64;
KEYCODE_ENVELOPE=65;
KEYCODE_ENTER=66;
KEYCODE_DEL=67;
KEYCODE_GRAVE=68;
KEYCODE_MINUS=69;
KEYCODE_EQUALS=70;
KEYCODE_LEFT_BRACKET=71;
KEYCODE_RIGHT_BRACKET=72;
KEYCODE_BACKSLASH=73;
KEYCODE_SEMICOLON=74;
KEYCODE_APOSTROPHE=75;
KEYCODE_SLASH=76;
KEYCODE_AT=77;
KEYCODE_NUM=78;
KEYCODE_HEADSETHOOK=79;
KEYCODE_FOCUS=80;//*Camera*focus
KEYCODE_PLUS=81;
KEYCODE_MENU=82;
KEYCODE_NOTIFICATION=83;
KEYCODE_SEARCH=84;
KEYCODE_MEDIA_PLAY_PAUSE=85;
KEYCODE_MEDIA_STOP=86;
KEYCODE_MEDIA_NEXT=87;
KEYCODE_MEDIA_PREVIOUS=88;
KEYCODE_MEDIA_REWIND=89;
KEYCODE_MEDIA_FAST_FORWARD=90;
KEYCODE_MUTE=91;
https://gist.github.com/davidnunez/1404789 ↩
动画插值器,用来描述动画的变化率,这里讨论的 Interpolator 指的是 android.animation
包下的 TimeInterpolator
。 以下所有的插值器都继承自 Interpolator , 而 Interpolator 接口直接继承自 TimeInterpolator , 自身并没有添加任何方法。
TimeInterpolator 中有
abstract float getInterpolation(float input)
方法,参数 input:input 参数是一个 float 类型,它取值范围是 0 到 1,表示当前动画的进度,取 0 时表示动画刚开始,取 1 时表示动画结束,取 0.5 时表示动画中间的位置。返回值:表示当前实际想要显示的进度。取值可以超过 1 也可以小于 0,超过 1 表示已经超过目标值,小于 0 表示小于开始位置。
input 的值只与时间相关,和我们设定没有任何关系。线性插值器函数实现:
public float getInterpolation(float input) {
return input;
}
加速插值器,开始很慢,不断加速
public AccelerateInterpolator(float factor)
将factor设置为1.0f会产生一条y=x^2的抛物线。增加factor到1.0f之后为加大这种渐入效果(也就是说它开头更加慢,结尾更加快)
当fractor不为1时,轨迹曲线是 y=x^(2*fractor)(0<x<=1)
的曲线
减速插值器,开始比较快然后减速的插值器,动画的快慢度。将factor值设置为1.0f时将产生一条从上向下的y=x^2抛物线。增加factor到1.0f以上将使渐入的效果增强(也就是说,开头更快,结尾更慢)
加速减速插值器,开始慢从中间后开始变快
线性插值器
弹跳插值器
回荡秋千插值器
正弦周期变化插值器
ImageView 的 ScaleType 属性决定了图片在 View 上显示时的样子,是比例缩放,还是显示图片的整体或者局部等等。对于一张图片,有其自身的大小,而 ImageView 也有其自身的大小,这两者如何完美的合作其结果很重要的设置便是 ScaleType 属性。
设置该属性的方式有两种:
android:scaleType="center"
imageView.setScaleType(ImageView.ScaleType.CENTER);
所有的 Scale 方式有 8 种,文档 中可以查看到,下文也会详细介绍。
按图片原大小居中显示,不缩放原图片。当原始图片长/宽超过View的长/宽,则截取图片的居中部分显示,当图片长/宽小于 View 长宽时,可能造成填充不完全, ImageView 周围有空白的情况。
在 xml 中使用定义: android:scaleType="center"
按比例扩大图片的size居中显示,不改变图片宽高比,使得图片长(宽)等于或大于View的长(宽),图片填充满 ImageView,可能产生裁剪,此时能保证 View 被完整填充。也就意味着这种裁剪方式会使 ImageView 被完整填充,对于高大于宽的图片,高部分就会被裁剪。
在 xml 中使用定义: android:scaleType="centerCrop"
将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽小于或等于View的长/宽,ImageView 可能产生空白部分。对于高度大于宽度图片,则高度填充整个 ImageView 高度,宽度则小于 ImageView 宽度,则宽度两边产生空白。
在 xml 中使用定义: android:scaleType="centerInside"
把图片按比例扩大/缩小到 ImageView 的高度或者宽度,使用 CENTER 方式居中显示,也就是使用该方式,使用图片长的一部分适应。
FIT_START
, FIT_END
在图片缩放效果上与 FIT_CENTER
一样,只是显示位置不同,FIT_START
是置于顶部,FIT_CENTER
居中,FIT_END
置于底部。
在 xml 中使用定义: android:scaleType="fitCenter"
、 android:scaleType="fitEnd"
、 android:scaleType="fitStart"
不按比例缩放图片,把图片塞满整个 View ,如果 View 与 原图片长宽不对应,可能造成图片长宽比改变,以及图片变形。
在 xml 中使用定义: android:scaleType="fitXY"
通过变化矩阵来设置 ImageView 大小。可以通过设置 matrix 然后手动利用 matrix 来调整 ImageView 可以实现 ImageView 的双指缩放和旋转。不过似乎用的很少。
具体细节可以继续再讨论。
如果要看效果图,这里 很不错,一目了然。
在 xml 中使用定义: android:scaleType="matrix"
每一种 View 都可以包含背景图片,而 ImageView 的 src 可以设置 ScaleType ,adjustViewBounds 等属性。
center, center_crop, center_inside, fit_center, fit_start, fit_end 等方式都会改变图片的比例,但是会有一定程度的裁剪。
使用如下 Layout
<ImageView
android:id="@id/img"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
使用 fitCenter
来使图片填充整个 ImageView 宽度,并且进行等比例放缩。
答案来自 StackOverflow
Android 的动画实现有不同的方式,在 3.0 以前 Android 的动画很简单只能在 View 层做,在后期版本中不断的加入动画实现,至今已经有这非常完善的动画系统了。Android 系统提供了 Property animation 和 View animation 两大动画系统。除去这两大动画系统之外,还有一类 Drawable Animation, 允许加载 drawable 并且一帧一帧播放:
另外官方文档也有另外一种分类方法 将 Tween Animation 补间动画 和 Frame Animation 逐帧动画归纳为 View Animation。 见文档
旧的动画系统,只能用来对 View 进行动画。View Animation 很简单,不过只能支持简单的缩放、平移、旋转、透明度基本的动画,且有一定的局限性。
Android中提供的 4种 View 动画:
以及 AnimationSet 动画集合,可以混合使用多种动画。 View Animation 都在 view.animation 包名下。
Android 系统在 3.0 之后推出了 Property Animation (属性动画)框架,属性动画可以用于任何对象,相比属性动画,View Animation (视图动画)的一个非常大的缺陷就是不具备交互性,当某个元素发生视图动画后,其响应事件的位置还依然是在动画前的地方,所以视图动画只能做普通的动画效果,避免交互的发生。但它的优点也非常明显,即效率比较高且使用方便。
视图动画使用非常简单,不仅可以通过XML文件来描述一个动画过程,同样可以使用代码来控制整个动画过程。
animation.setDuration(long durationMillis); // 设置动画时长
animation.setRepeatCount(int repeatCount); // 设置动画重复次数
animation.setFillAfter(boolean); // 动画执行完后是否停留在执行完的状态
animation.setFillBefore(boolean); // 动画执行前是否应用状态
animation.setStartOffset(long startOffset); // 动画执行前的等待时间
animation.setInterpolator(Interpolator i); // 设置动画插值
// 设置重复模式, 这个模式只有在 repeat count 大于0 或者 INFINITE 时才生效,有 RESTART 和 REVERSE 两种,RESTART 为重头播放,REVERSE 为逆向播放动画。
animation.setRepeatMode(int repeatMode);
animation.setStartTime(long startTimeMillis); // 设置动画开始时间
animation.start(); // 开始动画
animation.startNow(); // 立即开始动画
启动动画的两种方式:
view.startAnimation(animation);
//或者设置动画之后延后开始动画
view.setAnimation(animation);
animation.start();
当使用 xml 配置动画时,需要放到 res 目录下 res/anim/
。 xml 定义的 root 元素可以是 :
<alpha>
<scale>
<translate>
<rotate>
interpolator
元素<set>
或者 set 中包含其他 set举例:
<set android:shareInterpolator="false">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="700" />
<set android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:fromXScale="1.4"
android:toXScale="0.0"
android:fromYScale="0.6"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400"
android:fillBefore="false" />
<rotate
android:fromDegrees="0"
android:toDegrees="-45"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400" />
</set>
</set>
可以使用如下代码,从 XML 中加载动画:
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);
接下来就分别看一下不同 View Animation 的使用。
AlphaAnimation 动画,可实现淡入淡出。
AlphaAnimation(float fromAlpha, float toAlpha)
参数说明:float 设置透明度渐变动画, 1.0 完全不透明, 0.0 是完全透明
然后利用 View 的 startAnimation 方法就可以使用。
旋转动画,可以在 X-Y 平面设置旋转的动画,可以指定旋转中心, (0,0) 表示 top left 坐点,如果不指定,默认为左上角为旋转中心。
有四个构造函数:
RotateAnimation(Context context, AttributeSet attrs)
// 默认旋转中心为 (0,0)
RotateAnimation(float fromDegrees, float toDegrees)
RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY)
RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
参数说明:
Animation.ABSOLUTE
、RELATIVE_TO_SELF
、RELATIVE_TO_PARENT
。决定了 pivotXValue 的方式。ABSOLUTE
、RELATIVE_TO_SELF
、RELATIVE_TO_PARENT
。放缩动画,可指定缩放中心。
ScaleAnimation(Context context, AttributeSet attrs)
ScaleAnimation(float fromX, float toX, float fromY, float toY)
ScaleAnimation(float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)
ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
参数说明:
Animation.ABSOLUTE
, Animation.RELATIVE_TO_SELF
,Animation.RELATIVE_TO_PARENT.
.位移动画,可以控制位置的动画
TranslateAnimation(Context context, AttributeSet attrs)
TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue)
参数说明:
Animation.ABSOLUTE
, Animation.RELATIVE_TO_SELF
, or Animation.RELATIVE_TO_PARENT
Android 3.0, API 11 及以上,可以对任意类型进行动画。 属性动画在系统的 android.animation 包名下。总的来说,属性动画就是,动画的执行类来设置动画操作的对象的属性、持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态的变化对象的属性。
可定义如下参数:
addListener(Animator.AnimatorListener listener); // 添加监听器,可以手动设置监听,手动设置属性
isRunning(); // 当前动画是否在执行
isStarted(); // 动画是否开始
isPaused(); // 暂停状态
setDuration(long duration); // 设置动画时长,单位为毫秒
setInterpolator(TimeInterpolator value); // 设置动画插值
setStartDelay(long startDelay); // start() 方法被调用之后延时
setTarget(Object target); // 设置动画主体
start(); // 开始动画
cancel(); // 取消动画
static ValueAnimator ofArgb(int... values); // 颜色动画
static ValueAnimator ofFloat(float... values)
static ValueAnimator ofInt(int... values)
static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
setRepeatCount(int value); // 可选值 INFINITE 无限循环动画
setRepeatMode(int value); // RESTART,REVERSE
Object getAnimatedValue(); // 运动时,当前运动点的值
// 监听动画变化时的实时值
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
// 添加方法为:public void addUpdateListener(AnimatorUpdateListener listener)
// 监听动画变化时四个状态
public static interface AnimatorListener {
void onAnimationStart(Animator animation); // 动画开始
void onAnimationEnd(Animator animation); // 动画结束
void onAnimationCancel(Animator animation); // 动画 cancel
void onAnimationRepeat(Animator animation); // 动画重复
}
//添加方法为:public void addListener(AnimatorListener listener)
removeAllListeners();
removeListener(Animator.AnimatorListener listener);
removePauseListener(Animator.AnimatorPauseListener listener);
默认的 ValueAnimator 使用非线性的插值器 interpolation,加速进入减速退出,可以通过 setInterpolator(TimeInterpolator) 来改变默认的行为。
可以类似 View Animation 定义 xml 来创建动画, 动画资源文件需要保存在 res/animator/
目录下。Animator 动画支持:
<animator>
<objectAnimator
<set>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse"/>
<!-- 同样可以定义动画集合,顺序执行动画 -->
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
定义动画资源之后可以使用如下代码使用动画:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.animator.property_animator);
set.setTarget(myObject);
set.start();
更多XML定义的语法可以参考官方 文档
ObjectAnimator 动画的执行类,后面详细介绍 ValueAnimator 动画的执行类 AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。 AnimatorInflater 用户加载属性动画的xml文件 TypeEvaluator 类型估值,主要用于设置动画操作属性的值。 TimeInterpolator 时间插值,有很多子类,可以具体展开再讲,AccelerateDecelerateInterpolator, AccelerateInterpolator, AnticipateInterpolator, AnticipateOvershootInterpolator, BaseInterpolator, BounceInterpolator, CycleInterpolator, DecelerateInterpolator, FastOutLinearInInterpolator, FastOutSlowInInterpolator, Interpolator, LinearInterpolator, LinearOutSlowInInterpolator, OvershootInterpolator, PathInterpolator
ValueAnimator animator = ValueAnimator.ofInt(0,1000);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
Log.d(TAG,"curValue: "+curValue);
}
});
animator.start();
同样也可以通过 ofFloat
, ofObject
等等静态方法构造不同类型的 ValueAnimator。然后通过监听器动态地更新属性。
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
ObjectAnimator 使用范围更广,但是同样也有很多约束条件。
可以在 res/drawable/
目录下定义 XML 文件:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
用如下代码,将 Drawable 附加到 ImageView 中:
AnimationDrawable rocketAnimation;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}
start()
方法不能在 onCreate()
方法中调用, AnimationDrawable 此时还没有附加到window 上,如果需要动画自动播放,可以在 onWindowFocusChanged()
中调用。
更多的 XML 语法可以参考官方文档
Java 语言中存在四种类型:
前三种是引用类型,类实例和数组是对象,基本类型不是对象。
在Java中一共有8种基本数据类型,其中有4种整型,2种浮点类型,1种用于表示Unicode编码的字符单元的字符类型和1种用于表示真值的boolean类型。(一个字节等于8个bit)
1.整型
类型 | 存储需求 | bit数 | 取值范围 | 备注 |
---|---|---|---|---|
byte | 1字节 | 1*8 | -128~127 | |
short | 2字节 | 2*8 | -32768~32767 | |
int | 4字节 | 4*8 | -2^31 ~ 2^31-1 | |
long | 8字节 | 8*8 | -2^63 ~ 2^63-1 |
2.浮点型
类型 | 存储需求 | bit数 | 取值范围 | 备注 |
---|---|---|---|---|
float | 4字节 | 4*8 | 2^-149 ~ (2-2^-23)·2^127 | float类型的数值有一个后缀F(例如:3.14F) |
double | 8字节 | 8*8 | 2^-1074 ~ (2-2^-52)·2^1023 | 没有后缀F的浮点数值(如3.14)默认为double类型, Double 有静态变量 MIN_VALUE 和 MAX_VALUE 。 |
3.char类型
类型 | 存储需求 | bit数 | 取值范围 | 备注 |
---|---|---|---|---|
char | 2字节 | 2*8 | 0 ~ 65,535 |
4.boolean类型
类型 | 存储需求 | bit数 | 取值范围 | 备注 |
---|---|---|---|---|
boolean | 1字节 | 1*8 | false、true |
补充:Java有一个能够表示任意精度的算术包,通常称为“大数值”(big number)。虽然被称为大数值,但它并不是一种Java类型,而是一个Java对象。
如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math
包中的两个很有用的类:BigInteger
和 BigDecimal
(Android SDK中也包含了java.math
包以及这两个类)这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。具体的用法可以参见Java API。
BigInteger 和 BigDecimal 都是不可变 immutable ,类似于 String, 在使用
BigInteger sum = new BigInteger.valueOf(0);
sum.add(BigInteger.valueOf(10)); // wrong way, sum is still 0
sum = sum.add(BigInteger.valueOf(10)); // right way, add() return a BigInteger Object.
优点:
Builder 模式,代码易读,模拟了具名的可选参数,build 方法可检验约束条件。 builder 可以自动填充某些域,比如每次创建对象的时自动增加序列号。
类的构造器或者静态工厂中具有多个参数时,设计这种类, Builder 模式可考虑,特别是当大多数参数都是可选的时候。
当类的参数初始化时有相互关联时使用 Builder 模式。
共有静态成员 final 域
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { … }
public void leaveTheBuilding() {…}
}
静态方法所有调用,都会返回同一个对象引用,不会创建其他 Elvis 实例。
公有的成员是静态工厂方法:
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { … }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {…}
}
希望一个类不能被实例化,则私有化构造方法
String s1 = "string";
String s2 = new String("string"); // don‘t do this
当心无意识的基本类型自动装箱。
Java 内存泄露可能发生在:
类自我管理内存
比如在实现 Stack 栈时,弹出栈时,消除对象引用,结束变量的生命周期。
提供显式的终止方法,要求类客户端在每个实例不再有用的时候调用这个方法。 Java 中 FileInputStream, FileOutputStream,Timer 和 Connection 都具有终结方法。
所有对象都通用的方法, 非 final 方法(equals、hashCode、toString、clone 和 finalize)都有明确的通用约定,被设计成被覆盖。
类的每个实例都只与它自身相等
什么时候覆盖 Object.equals ?
equals 等价关系:自反,对称,传递,一致
里氏替换原则 Liskov substitution principle ,类型的任何重要属性也将适用于它的子类型。
将类的基本信息放入。IDE 中一般可以自动生成该方法。
实现对象实例之间的比较。
encapsulation 封装,模块化
原因:有效地解除组成系统各个模块之间的耦合关系,使得模块之间可以独立开发、测试、优化、使用、理解和修改。
类成员的公开级别:
公有的静态 final 域来暴露常量,惯例,使用 大写字母 加 下划线,这些域要么包含基本类型,要么包含指向不可变对象的引用。
未来改变类的内部表示时非常有效,如果类是包级私有,或者私有嵌套类,直接暴露数据域并没有本质的错误。
不可变类 实例不能被修改,实例包含的信息必须在创建时提供,并且在对象生命周期内固定不变。 String 基本类型包装类, BigInteger 和 BigDecimal 是不可变类。
原因:不可变类易于设计、实现和使用,不容易出错,更加安全
类不可变类要遵循一下规则:
优点:1. 不可变对象本质上是线程安全的,他们不要求同步。2. 不可变对象可以被自由地共享,将频繁使用的值提供公有的静态final常量,将频繁被请求的实例缓存起来,当现有实例可以符合请求时,不用创建新的实例。基本类型的包装类和 BigInteger 都有这样的静态工厂。3. 不可变对象为其他对象提供了大量的构建。
缺点:不可变类唯一的缺点就是,对于每个不同的值都需要一个单独的对象。
不扩展现有的类,而是在新的类中增加一个私有域引用一个类的实例,这种设计叫做“复合” Composition。
优点:与方法调用不用,继承打破了封装性,子类依赖于父类中特定功能的实现细节,因此复合被提出,不扩展现有的类,而是在新的类中增加一个私有域来组成新的类。
缺点:包装类没什么缺点,但需要注意,包装类不适合用在回调框架 callback framework。
类必须有文档说明它可覆盖的方法的自用性。在发n布类之前编写子类进行测试。
为了允许继承,类需要遵守的约束条件:
两种方法禁止子类化:
Java 提供两种机制,来允许多实现,接口与抽象类。重要区别在于:
优点:
常量接口 constant interface ,不包含 任何方法,只包含静态final域,每个域都是一个常量。反面,不值得使用。
导出常量可选合理方案,将常量添加到 相关类 或者接口中,尽量使用枚举类型 enum type,否则应该使用不可实例化的工具类 utility class 来导出常量。
接口只应该被用来定义类型,不应该被用来导出常量。
标签类指在类中用标签区别类的行为模式,比如使用枚举变量,更加枚举不同显示不同内容。尽量不使用标签类,使用子类抽象,将标签类中每个方法都定义成一个包含抽象方法的抽象类。
允许程序把“调用特殊函数的能力”存储起来并且传递这种能力,这种机制允许函数调用者通过传递第二个函数,来指定行为。
执行对象上的某项操作,能够实现函数指针。
Comparator
嵌套类 nested class 指被定义在另一个类内部的类。
嵌套类有四种:
除去第一种之外,其他三类都被称为内部类 inner class。
静态成员类,最简单的嵌套类,普通类,静态成员类可以访问外围类的所有成员,包括私有。访问性原则同类中其他成员。
静态成员类,常见用法作为公有辅助类,与外部类一起使用时才有意义。
非静态成员类,语法层面只有一个 static 修饰符的不同,但是两者区别很大。非静态成员类的每一个实例都隐含着与外围类的一个外围实例 enclosing instance。嵌套的实例需要外围类的实例。
非静态成员类的一种常见用法是定义 adapter,它允许外部类的实例被看做是另一个不相关的类的实例。
私有静态成员类 常见用法是代表外围类所代表的对象的组件。
匿名类没有名字,在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。
匿名类的一种常见用法是动态地创建函数对象 function object。 另一种常见用法是创建过程对象 process object ,比如 Runnable , Thread 或者 TimeTask 实例。第三种常见用法是在静态工厂方法的内部。
局部类是嵌套类中用得最少的类。任何可以声明局部变量的地方都可以声明局部类。
成员类的每个实例都需要一个指向外围实例的引用,就要把成员类做成非静态的,否则做成静态的。
嵌套类属于方法内部,只需要一个地方创建实例,匿名类,否则就是局部类
尽早发现错误,最好是编译时就发现。 在 Java 1.5 之前,运行时,集合,才会发现错误。
非受检强制转化警告 unchecked cast warnings
非受检方法调用警告
非受检普通数组创建警告
非受检转换警告 unchecked conversation warnings
尽可能小的使用 SuppressWarnings(“unchecked”) 注解
数组是协变 covariant 的,泛型是不可变的 invariant。
数组在运行时才知道并检查元素类型。泛型在编译时就能检查类型。
编写泛型方法和编写泛型类相似
泛型方法的特性是,无需明确指定类型参数的值,编译器通过检查方法参数的类型来计算类型参数的值。类型推导。
参数化类型是不可变的
Interface Iterable<T>
接口,实现这个接口可以让对象使用 foreach 循环,或者使用
// If you have Iterable<String> , you can do:
for (String str : myIterable) {
...
}
大部分的容器类都实现了这个接口。
http://docs.oracle.com/javase/6/docs/api/java/lang/Iterable.html
Java 提供了特殊的参数化类型,有限制的通配符类型 bounded wildcard type
枚举和注解, Java 1.5 版本中增加两个引用类型家族:1. 新的类枚举类型(enum type) 2. 新的接口注解类型(annotation type)。
枚举类型是指由一组固定常量组成合法值的类型。
优点:
所有枚举都有 ordinal 方法,返回枚举常量在类型中的位置,不要使用该方法。永远不要根据枚举的序数导出与它关联的值,而是应该将它保存到实例域中。
不必定义注解类型,但是都应该使用 java 平台所提供的预定义注解类型。
标记接口 marker interface 没有包含任何方法声明的接口,指明一个类实现的某种属性的接口。
设计方法的参数,返回值
在传入可变参数时需要特别注意,外部可能会通过改变传入参数而间接的影响到类内部的实现。因此需要在初始化的时候进行保护性拷贝。
谨慎地选择方法名称,遵循命名习惯
不过于追求提供便利的方法
避免过长的参数列表
有三种方法可以缩短长参数列表
将方法拆解成多个方法
创建辅助类 helper class , 保存参数分组,一般作为静态成员类
从对象构建到方法调用都采用 Builder 模式,多次 setter
对于参数类型,要优先使用接口而非类, Map接口作为参数,可以传入 Hashtable, HashMap,TreeMap,TreeMap 子映射表submap 等等
对于 boolean 参数,优先使用两个元素的枚举类型
永远不要导出两个具有相同参数数目的重载方法。
Java 1.5 版本中增加了 可变参数 varargs 方法,一般称为 variable arity method 。可匹配不同长度的变量的方法。
可变参数方法接受0个或者多个指定类型的参数,先创建一个数组,数组大小为在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法。
方法的文档应该简洁地描述出它和客户端之间的约定。
几乎每一个局部变量的声明都应该包含一个初始化表达式。
有三种情况无法使用 for-each 循环
使用标准类库,可以充分利用这些编写标准类库的专家知识,以及在你之前其他人的使用经验。
将时间花在应用程序上,而不是底层细节。
都应该熟悉 java.lang java.util java.io 中的内容。
java.util.concurrent 并发工具
BigDecimal
使用 int 或者 long 数值范围没有超过9位十进制数,可以使用int,没有超过18位,可用 long ,超过18位,就必须使用 BigDecimal
基本类型 primitive int double boolean
对应的引用类型 reference type ,称为装箱基本类型 boxed primitive ,对应为 Integer、Double、Boolean
何时使用装箱基本类型:
字符串不适合代替其他的值类型
字符串不适合代替枚举类型
字符串不适合代替聚集类型,如果需要用 String 来描述实体,通常不建议这样做,通常应该用一个私有静态成员类来描述。
字符串不适合代替能力表 capabilities
如果可以使用更加合适的数据类型,或者可以编写更加合适的数据类型,就应该避免使用字符串来表示对象。
字符串连接操作符 “+”
获得可接受的性能,使用 StringBuilder 代替 String append 方法
使用接口作为类型,程序会更加灵活
核心反射机制 core reflection facility java.lang.reflect 通过程序来访问关于已装载的类信息的能力。给定 Class 实例,可以获得 Constructor Method 和 Field 实例。
丧失了编译时类型检查的好处
执行放射访问所需要的代码笨拙冗长
性能损失
反射功能只是在设计时被用到,通常普通应用程序在运行时不应该以反射方式访问对象。
JNI 方法调用本地 Native method
不要试图去编写快速的程序—-应该努力编写好的程序,速度随之而来。再设计系统时,设计API、线路层协议和永久数据格式时候,一定要考虑性能因素。
包名,层次,句号分割,小写字母和数字(少使用) 不以 java 和 javax 开头
包名其余部分通常不超过8个字符
Java 提供三种可抛出结构:
受检的异常 checked exception
运行时异常 run-time exception
错误 error
期望调用者能够适当地恢复,使用受检异常。
用运行时异常来表明编程错误。
runtimeException 子类
1、 java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
2、java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。
3、java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
4、java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
5、java.lang.NegativeArraySizeException 数组长度为负异常 6、java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常 7、java.lang.SecurityException 安全性异常 8、java.lang.IllegalArgumentException 非法参数异常
2.IOException IOException:操作输入流和输出流时可能出现的异常。 EOFException 文件已结束异常 FileNotFoundException 文件未找到异常
并发
synchronized 保证同一个时刻只有一个线程执行某一段代码块
当多个线程共享可变数据时,每个读或者写数据的线程都必须执行同步。
文档说明类是否可以被多个线程安全使用
不可变 immutable 类实例不可变,不需要外部同步,String Long BigInteger
无条件的线程安全 unconditionally thread-safe 实例可变,但是有足够的内部同步 ,实例可被并发使用,无需外部同步, Random ConcurrentHashMap
有条件的线程安全 conditionally thread-safe Collections.synchronized
非线程安全 not thread-safe 类实例可变,需要外部同步 ArrayList HashMap
线程对立 thread-hostile 类不能安全地被多个线程并发使用
依赖于线程调度器的程序,很有可能都是不可移植的。
序列化
将对象编码成字节流,对象序列化
序列化成功需要:
Externalizable 接口继承了 java 的序列化接口,并增加了
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
为继承而设计的类,尽量避免去实现 Serializable 接口。
内部类不应该实现 Serializable, 静态成员类可以
在拿到 moto 360 2代国行之前就已经做过调研, 国行并不支持国际版 Android Wear 程序同步,需要用国内阉割版 Android Wear 连接,并且不能使用 Play Store 中各种定制表盘及Android wear 应用。虽然可以通过使用 国际版 Android Wear 1.3 版本连接并同步 moto 360 来激活并使用各种表盘及应用,但是拿到手使用一个星期之后,我依然还是对国行 moto 360 2代表示失望。
在没有了 Google Now 的情况下,语音几乎是不可用状态,国行所带的出门问问,在国际版 Android wear 激活的情况下几乎不可用,真实使用一次都没成功。当然在国内版 Android wear激活的情况下,可能会有一些用处,但是Android wear 的精华就是 Google Now,没有了核心的支持,几乎就沦为只能看时间,只能收通知的尴尬境地。
不过值得赞扬的是续航能力,在一周的使用情况下,12h的待机下来基本还能保证40%以上的电量,这一点值得称赞,但是几乎是一天一冲的情况下,如果能够将续航能力再提高,或许更能吸引用户。单独的无线充电技术,在使用过程也并没有出现太大的问题,至于网上曾经说过的位置放置稍有偏差就无法充电的问题也并没有遇到。
在目前给手表适配的app还不多的情况下,要给微信给个赞,在国际版 Andorid wear 匹配链接下不仅能够收通知,并且在回复时提供表情,语音,语音转文字三种方式,并就使用过程来说,中文识别并没有出现太大问题,语音消息声音大小也合适,至于表情目前似乎只能有内置的一些可选,但是也足够使用。
另一个不错的feature就是这个通知静音的设置,表盘上,从上网下可以调出设置。这个功能本来也是我在原生 Android 上非常喜欢的,可以设置自动睡眠静音,自动在Google日历上出现Event时静音。我希望在我规划好的时间里不被打扰。当然手表作为通知的扩展他的提醒也应该遵循手机的设置。
另外说到二代 6.0.1 的升级,在和国际版相比不支持 WIFI,不支持手势的差距下,在更新了 6.0.1 之后手势的功能被补了回来。
在购买 Moto 360 2代之前,抱有的期望有以下几点:
基于以上的期望,到现在来看
新的 Android Wear 基本就是 Android 通知的外延,Google 在对 Android Wear 的设计中就将其操作固定成卡片式的左右滑动,不同应用间的通知使用上下滑动切换,相同通知的内容,通过向左滑可以进行更多的操作。
在表盘界面,向左滑可以调出所有应用;先下滑可以调出通知设置,静音设置,还有影院模式,进入设置等操作;向上滑动可以调出系统通知,如果不存在未读通知则不显示任何通知。
长按表盘可以更换表盘设计,当然这一点非常值得称赞,我不希望生产商在手表出厂就固定了一套表盘界面,而后就再无法更改,而这一点正是 Moto 360 ,以及 Android Wear 值得称赞的地方,并且将表盘设计开放给第三方能够带来无穷的想象力结果,喜欢指针手表,拿就可以选择指针的表盘设计,如果喜欢电子表盘,也可以选择电子表盘。
在来通知时,通过向右滑动可以忽略消息,向左滑动可以进行更多的操作,比如Gmail,可以选择存档,回复,删除等等;微信可以选择回复,忽略等等。
在早先手机上,Google Fit曾经出现没有再后台自动启动服务而暂停记录的情况,通过 Watch 带了了更多的数据,并且也希望能够对 Google Fit 的数据做更多的补充。如今市面上的“智能手表”,“智能手环”如果不能记步,不能记录心率都快称不上智能设备了。在调研的过程中获知2代 Moto 360 其实并不带 GPS 功能,如果想要使用记录路径的话,其实使用的依然是手机的 GPS,虽然这一点有点不便,但是鉴于记录路径这个功能其实日常并不常用,并且带来的电量消耗也会随之增加,所以在运动版和普通版之间还是选择了普通版,不过有机会还是要尝试一下运动版的。
最近项目使用,总结了目前 Android 中使用到的人脸检测技术,主要分成三部分来介绍:
接下来就依次介绍前三种人脸检测的方法。
android.media
包中的人脸检测API 有如下两个限制:
只要注意这两个限制,另图片眼睛的距离不要太小,其他的代码核心的没几句话。
/**
* There are some limitation in this 用android.media 包中识别人脸package.
* 使用使用使Face Detection API's input Bitmap must :
* <p/>
* 1. config with Config.RGB_565<br/>
* 2. Bitmap width must be even<br/>
* <p/>
* more details can be checked
* http://stackoverflow.com/q/17640206/1820217
*
* @param bitmap Bitmap
*/
private void detectUsingNative(final Bitmap bitmap) {
if (null == bitmap || isRunning) {
if (listener != null) {
listener.onFail();
}
return;
}
facesCount = 0;
final android.media.FaceDetector faceDetector = new android.media.FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MEDIA_MAX_DETECT_FACE_NUMBER);
androidNativeFacesResults = new android.media.FaceDetector.Face[MEDIA_MAX_DETECT_FACE_NUMBER];
final Handler handler = new Handler();
thread = new Thread() {
@Override
public void run() {
facesCount = faceDetector.findFaces(bitmap, androidNativeFacesResults);
handler.post(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onSuccess();
}
}
});
isRunning = false;
}
};
thread.start();
isRunning = true;
}
Play Service 中的人脸检测是随着 Mobile Vision API 一同出现的,这个库中还有一些其他的API,比如识别二维码,识别文字等等,并且 Play Service 中的人脸识别更准确的说应该叫人脸追踪,在官方实现的 Demo 中,直接调用手机摄像,能够一直追踪镜头中的同一人头像。
以下是部分实现,详细代码可参考文末给出的 GitHub 代码。
/**
* 使用 Play Service 中人脸检测
*
* @param bitmap Bitmap
*/
private void detectUsingGms(Bitmap bitmap) {
if (null == bitmap) {
if (listener != null) {
listener.onFail();
}
return;
}
facesCount = 0;
detector = new FaceDetector.Builder(context)
.setTrackingEnabled(false)
.setLandmarkType(FaceDetector.ALL_LANDMARKS)
.setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
.build();
// This is a temporary workaround for a bug in the face detector with respect to operating
// on very small images. This will be fixed in a future release. But in the near term, use
// of the SafeFaceDetector class will patch the issue.
Detector<Face> safeDetector = new SafeFaceDetector(detector);
// Create a frame from the bitmap and run face detection on the frame.
Frame frame = new Frame.Builder().setBitmap(bitmap).build();
faces = safeDetector.detect(frame);
if (!safeDetector.isOperational()) {
// Note: The first time that an app using face API is installed on a device, GMS will
// download a native library to the device in order to do detection. Usually this
// completes before the app is run for the first time. But if that download has not yet
// completed, then the above call will not detect any faces.
//
// isOperational() can be used to check if the required native library is currently
// available. The detector will automatically become operational once the library
// download completes on device.
if (listener != null) {
listener.onFail();
}
return;
}
if (listener != null) {
listener.onSuccess();
}
}
Face++ 提供了联网的人脸检测服务,需要到其网站上注册开发者账号获取API使用权限。其大概检测代码如下:
/**
* 使用 Face++ 人脸检测
*
* @param file File
*/
private void detectUsingFacePlus(File file) {
if (!file.exists() || isRunning) {
if (listener != null) {
listener.onFail();
}
return;
}
final PostParameters parameters = new PostParameters();
parameters.setImg(file);
final Handler handler = new Handler();
facesCount = 0;
thread = new Thread() {
@Override
public void run() {
boolean hasFace = false;
boolean detectSucceed = false;
Log.d("FacePlusDetect", "Detect Request :" + parameters.toString());
HttpRequests httpRequests = new HttpRequests(FACEPLUSPLUS_APIKEY, FACEPLUSPLUS_APISECRET, false, true);
JSONObject result;
try {
result = httpRequests.detectionDetect(parameters);
if (result != null) {
detectSucceed = true;
JSONArray faces = result.getJSONArray("face");
double imgWidth = result.getDouble("img_width");
double imgHeight = result.getDouble("img_height");
if (faces != null && faces.length() > 0 && null != listener) {
// Has face!!
facesCount = faces.length();
facePlusResults = new RectF[facesCount];
hasFace = true;
for (int i = 0; i < facesCount; i++ ){
float x, y, w, h;
facePlusResults[i] = new RectF();
// 需注意返回结果的center,width,height 都为0~100,百分比
x = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("x");
y = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("y");
w = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("width");
h = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("height");
float realx = (float) (x * imgWidth / 100);
float realy = (float) (y * imgHeight / 100);
float realw = (float) (w * imgWidth / 100);
float realh = (float) (h * imgHeight / 100);
facePlusResults[i].set(realx - realw /2,
realy - realh / 2,
realx + realw / 2,
realy + realh / 2);
}
// String genderStr = playServiceFaces.getJSONObject(0).getJSONObject("attribute").getJSONObject("gender").getString("value");
// gender = Gender.getValueOf(genderStr);
} else {
hasFace = false;
// detectSucceed = true;
// gender = Gender.OTHER;
}
// Log.d("FacePlusDetect", "Detect Result : hasFace = " + hasFace + "; gender = " + gender.toString());
}
} catch (FaceppParseException e) {
detectSucceed = false;
Log.d(TAG, "Detect FaceppParseException !");
e.printStackTrace();
} catch (JSONException e) {
// if (hasFace) {
// gender = Gender.OTHER;
// }
Log.d(TAG, "Detect JSONException !");
e.printStackTrace();
}
if (detectSucceed) {
handler.post(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onSuccess();
}
}
});
} else {
handler.post(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onFail();
}
}
});
}
isRunning = false;
}
};
thread.start();
isRunning = true;
}
FaceDetectDemo 代码可参考:https://github.com/einverne/AndroidFaceDetectDemo
全部代码可参考 : https://github.com/einverne/Android-Face-Recognition
之前遇到的一个问题,安装 Linux Mint 的系统分区快要满了,但是我又不想重装系统,于是就提出来这样的一个问题。当时整天得想着如何解决这样的一个问题比较好,于是有了这篇文章。当然也借由这篇文章讲述一个复杂问题的提出到解答的整个过程。其他类似问题的解决过程也是类似的。
问题:整体备份 Linux 系统,免去重装系统,进行各种配置,以及安装各种应用的麻烦
问题相关:Windows 下有 Ghost 类似的工具可以协助完成 Windows 系统镜像的制作,并且可以完整恢复系统,而 Mac 下有 Time Machine 类似的工具,似乎可以还原整个系统。我想 Linux 下应该也存在类似的工具。
问题解决:经过Google,Linux 下备份系统的方式可以有很多,我不想使用命令行,如果有现成工具最好,最后锁定关键词 “Clonezilla”,一款非常强大的备份工具,可以用来备份硬盘,分区到镜像,或者直接写入其他硬盘或者分区。
之后搜索 Clonezilla 相关教程以及使用,借助 YouTube 熟悉使用过程,下载iso镜像,安装硬盘,制作启动U盘,从U盘启动电脑,熟悉电脑硬盘的名称,sda1,sdb1,sdb2,sdc1,类似的名称,基本上 sda 就是一块硬盘,后面接的数字是分区,在使用 Clonezilla 的过程中一定要小心数据,目标一定要指定空分区,或者空磁盘,否则目标磁盘的数据会全部被清除。
在我的真实例子中,我的 Linux Mint 安装在一块硬盘的一个分区中,利用命令或者GUI,查看该分区的名称,然后我的解决方法是给电脑新安装了一块 SSD,将光驱位换了。然后给该硬盘分区,并查看该分区的名称,然后利用学习到的 Clonezilla 来完成系统从一个分区到另一个分区的克隆。
从上图就可以看出,我是将 /dev/sdb8
分区拷贝到 /dev/sda1
分区。
如下使用 sudo blkid
查看 UUID。
/dev/sda1: UUID="a7a98d76-5dab-4272-8b9a-b82042b279c5" TYPE="ext4"
/dev/sdb1: LABEL="Program" UUID="000E3FDB00097ED7" TYPE="ntfs"
/dev/sdb5: LABEL="Document" UUID="000C3A300002F285" TYPE="ntfs"
/dev/sdb6: LABEL="Media" UUID="0005653100096CB5" TYPE="ntfs"
/dev/sdb7: UUID="ad6f91df-ba08-4fad-8efc-ac1254320e2d" TYPE="swap"
/dev/sdb8: UUID="5f920149-5676-46ef-b545-e50be77c65e2" TYPE="ext4"
/dev/sdc1: LABEL="System" UUID="EEBACEF9BACEBCF9" TYPE="ntfs"
/dev/sdc2: UUID="8A0005F80005EBCF" TYPE="ntfs"
在完成从分区到分区的克隆之后,修复系统磁盘 UUID 以及启动引导 grub。这里花费了一些时间去了解 grub 的启动过程。学习了一些命令。
sudo blkid #查看磁盘UUID
sudo fdisk -l #查看磁盘
vim /etc/fstab #磁盘的信息
vim /boot/grub/grub.cfg #启动引导的信息
# 大部分的情况下不需要直接修改这两个文件,查看一下内容即可,使用其他命令更新,无需手动的修改里面的值。
update-grub2 #更新启动引导
由于我在使用 Clonezilla 备份恢复的时候使用的是从硬盘一块分区拷贝到另一块硬盘分区,所以在完成备份成功之后两款分区的 UUID 是一样的,这样就导致开机引导的时候总是回到原来的系统,需要给新的分区重新指定一个 UUID,然后更新 grub 才能完成启动到新的硬盘分区。可以通过下面的教程完成 UUID 的修改。
至于修改 Linux 分区的 UUID 其实是另一个问题,当完成从问题提出到解决的过程就能解决。
以下为翻译,原文见[参考]1
Linux 分区的 UUID 的全称是 Universally Unique IDentifier。 这个 ID 会被用在不同的地方用来标识硬盘分区。最常见的就是在 /etc/fstab
文件中, 这个文件用来再系统启动时挂载分区。以下是一个小例子:
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc nodev,noexec,nosuid 0 0
# / was on /dev/sdc3 during installation
UUID=9467f4de-4231-401f-bcaa-fee718d49e85 / ext4 errors=remount-ro 0 1
# swap was on /dev/sdb1 during installation
UUID=aabe7e48-2d11-421f-8609-7ea9d75e7f9b none swap sw 0 0
理论上来讲创建两个 UUID 相同的几率是非常小的,可以参考 Random UUID , 但是如果使用 DD 或者 Clonezilla 在同一台设备克隆并恢复了分区,那么就有可能导致完全一样的分区 ID。
使用以上两个命令将会创建两个一模一样的分区,包括 UUID。在这样的情况下,就需要手动更改 UUID。
第一步,首先利用 sudo blkid
来获取分区标示。
/dev/sdb1: UUID="aabe7e48-2d11-421f-8609-7ea9d75e7f9b" TYPE="swap"
/dev/sdc1: UUID="9467f4de-4231-401f-bcaa-fee718d49e85" TYPE="ext4"
/dev/sdc3: UUID="93a54a4a-e0f5-4152-ae59-2245e8d16ee4" TYPE="ext4"
/dev/sde5: UUID="9467f4de-4231-401f-bcaa-fee718d49e85" TYPE="ext4"
/dev/sde6: LABEL="var" UUID="30433f28-1b79-4b4d-9985-fef5b1c886b5" TYPE="ext4"
如上图可以看出 /dev/sdc1
和 /dev/sde5
的 UUID 完全一致。第二步,使用命令 uuidgen
产生新的 UUID
uuidgen
f0acce91-a416-474c-8a8c-43f3ed3768f9
最后将新的 UUID 应用到新的分区中,
sudo tune2fs /dev/sde5 -U f0acce91-a416-474c-8a8c-43f3ed3768f9
最后,更新 grub 来使用新的 UUID,以免导致系统的混乱。
什么都不多说,这个神器就如 Chrome 下的 Tampermonkey,有很多神奇的待发现。
更新及 change log: http://forum.xda-developers.com/showthread.php?t=3034811
例如:Nexus 6 CPU芯片是 armv7,选择 arm 即可。 Play Store 上有一个 Hardware Info 的 APP,可以查看 CPU 架构。
然后需要确认手机的 SELinux 设置成 Permissive , 可以使用 SELinuxModeChanger 一款修改 SELinux 的应用。不过 N6 默认是 Permissive 的,也就不用修改了。
准备好apk,和 zip 包,最好也下载好 xposed-uninstaller*.zip ,当刷入 zip 包之后无限启动的时候可以恢复。不过最好的方式还是通过 recovery 整体备份原来的数据 Nandroid backup 。数据是最重要的!!切记。
Android 6.0 的 framework zip 包在 官网 可以下到。 而 APK 在 Xda 论坛可以找到。
通过 recovery 刷入 flash zip 之后进入系统会很长时间,之后启动 Xposed Installer ,enable module 之后需要 soft reboot 。 切记需要点击 Xposed Installer 中的 reboot ,不能自己手工 reboot ,否则无法识别 module,掉进这个坑,重启无数次无效。
经过反复尝试保留下来的一些modules,
屏蔽广告,另外一个可选择的屏蔽广告的 module 叫做 MinMinGuard,还没尝试,效果应该也很好。很多 ROM 集成的 Adaway 似乎也不错。
权限管理,一张图解释所有
模拟地址,但是 XPrivacy 也是可以有相同的功能的。但是最后还是另一个 module 叫做 Xposed Pokemon 好用。
可以定制很多部分,包括状态栏,导航栏,感觉最有用的就是可以定制长按 recent 按钮弹出小的 Launcher。然后自定义快速点击 recent 两次切换最近使用应用也是很有用的。还可以给状态栏加上网速监测。
自用上这个功能感觉已经不在需要刷其他ROM来支持一些特殊功能了,完全原生+Xposed 就已经让我很舒服了。
休眠后台服务,很好的App。
原生 Android 的 Smart Lock 只有根据蓝牙,GPS和侦测随身携带,这个 module 增加了可以根据 WIFI,或者 mac 地址或者 LAC CID 来增加信任的地点,感觉这个更加实用。日常实用 WIFI 的地方一般都是自己熟悉的地方,将那些地方的 WIFI AP name 或者 WIFI Mac 地址加入信任列表,这样就不用总在熟悉的地方解锁解锁解锁了。自己熟悉的地方也总不至于丢手机的吧。
很强大的转发,原先一直想要的转发功能都能搞定了。
微信不撤回
下载 Instagram 的图片,自从 IFTTT 不让我自动下载图片之后就诞生了这种需求。也正是因为这个需求让我发现并使用了 Xposed,然后又间接的找到了很多好玩的 module ,不过后来又找到了自动下载 ins 照片到 Google Drive 的方法。
修改运动记步频率,还挺神奇的。
在导航栏或者状态栏显示音乐波形,太赞了。
使用之后感觉不太需要的 module ,但是很强大的 module
很复杂的但是很强大的权限管理,非常细节,但是对新手不好,使用非常复杂。对权限要求比较高的可以尝试一下。
链接: http://repo.xposed.info/module/biz.bokhorst.xprivacy
高度自定义状态栏
和之前使用的 tint status bar 效果差不多,根据应用颜色改变状态栏和导航栏的颜色
自动改变键盘的颜色,我使用 TouchPal 所以也用不到。
修改系统字体
查看那些服务,禁用服务,省电
所有的 Xposed Module 都可以在 http://repo.xposed.info/module-overview 这里找到。当然 Xda 也有很多更新内容。