JAVA的一些注意事项
在用数字变量x和y拼接字符串xy的时候,不能使用x+y+“”,要用""+x+y,否则拼出来的就是先相加后变的字符串。
安全防护要全方位
在进行安全排查的时候,不仅要考虑正常情况下应该要做好防护,还需要考虑在异常场景下的防护,比如下面,在trycatch后,没有成功设置dtd,竟然也返回了构造的对象,这样就相当于异常场景么有进行防护设置了。
private SAXParserFactory newSecurityInstance()
{
SAXParserFactory instance = SAXParserFactory.newInstance();
try
{
instance.setFeature(DISALLOW_DTDS_FEATURE, true);
instance.setFeature(PARAM_FEATURE, false);
instance.setFeature(GENERAL_FEATURE, false);
}
catch (ParserConfigurationException | SAXNotRecognizedException |SAXNotSupportedException e)
{
RUNLOGGER.error("failed to set feature: " + PARAM_FEATURE + "false", e);
}
return instance;
}
覆写方法的可能性
子类对父类的方法进行覆写:
- 首先入参是不能变的,
- 回参的话,子类的回参必须是父类回参的子类。
- 访问限定符的话,JAVA里没有任何要求,你可以把一个protected的方法在子类里覆写成public。所以安全编程规范里要求,安全敏感的方法,不允许子类覆写方法然后更改方法的可访问性。
浮点数计算
浮点数中,竟然还隐藏了两个特殊值,分别是NaN和Infinity(就跟对象有一个Null),这两兄弟在计算的时候有很多坑,所以在使用浮点数进行计算的时侯,要特别小心。
例子:1/0.0会得到Infinity。cos(Infinity)会得到NaN。
大数运算最好还是基于BigInterger或者BigDecimal吧。
锁
- Boolean这种对象是绝对不能用来当锁的,因为它全局都只有两个实例,Boolean.true和Boolean.false。
- 同理,还有通过自动包装的基本类型也是坑,因为它们的-128-127范围内的Integer会共享实例。
- 同理,String的字面量,以及String对象调用Intern()方法后都是从常量池拿对象,也都是共享的实例。(new String(“aaa”)这样没事。“aaa”或者new String(“aaa”).intern()不行)
- 不能使用getClass()方法获得的类对象进行加锁,因为你可能会有子类继承了这个方法,那获得的对象就可能不一样了。
综上,最好还是用new Object()来当锁最保险。
Gerrit
gerrit的change-Id本质上就是为了保证cherrypick的时候,不重复创建评审任务,因为cherrypick生成的commit是不一样的。
hook-msg的作用就是在commit的时候帮助你创建changeId放在commit message里面,让gerrit能够找到change-Id。
push到review仓库git push origin HEAD:refs/for/demobox
Android的几个version
- compileSdkVersion是指编译的版本,是编译时的约束,不影响编译后的运行态,主要是做静态代码检查之类的;
- minSdkVersion是指软件最低的运行时Android版本,低于这个版本的机器就不能装你的软件;
- targetSdkVersion是指软件运行时的最高版本,是告诉系统说,这个版本有的所有功能你都能够支持的很好;
所以这三者的关系是minSdkVersion<=targetSdkVersion<=compileSdkVersion
android初始化界面
setContentView一定要写在findViewById前面,不然你拿到的只会是个null,而不是你期望的View
android的ID
我们设置的id是id的key而已(String类型),Id的值是int类型。所以不要指望通过Id的字符串来传递信息。
ADB
adb默认的端口是5037,如果是端口被占用了,ADB起不了,AVD也起不了,而且报错可能一点都不详细。这个时候要去看看端口占用的情况。
windows端的一些命令:
netstat -ano|findstr 5037
查看占用端口的pid
tasklist|findstr pid
查看某个pid的程序
taskkill /pid pid /F
删除某个进程
解决方法:一是把占用端口的进程干掉;二是把adb默认的端口改掉,修改环境变量ANDROID_ADB_SERVER_PORT
安卓中悬而未决的问题
息屏亮屏:
// 在onCreate中使用下面两种方式的一种,都会出现使用keyevent 26或者223/224时,第二次息屏亮屏后,屏幕有显示,却无法操作屏幕的问题。
// 方式一
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED|WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
// 方式二
KeyguardManager keyguardManager = (KeyguardManager)getSystemService(KEYGUARD_SERVICE);
KeyguardManager.KeyguardLock keyguardLock = keyguardManager.newKeyguardLock("unLock");
keyguardLock.disableKeyguard();
// 半成的解决方式: 在onResume里面,使用方式一。这解决了多次息屏亮屏后屏幕无法操作的问题,猜测是因为每一个window都需要取消一次锁屏才行。但是此时却发现,息屏亮屏后,切换别的应用,会直接进入锁屏界面。
// 七成的解决方式:在OnResume里面使用方式一,在onCreate里面使用方式二。这样就能解决切应用后,别的应用是直接锁屏,但是还是有一个问题,就是切到别的应用后,别的应用汇出现可以亮屏但是无法操作的情况。
安卓签名
安卓系统应用需要用系统的公钥(platform.x509.pem)私钥(platform.pk8)进行签名,可以用两种方式进行
-
先打包apk,然后用安卓自带的工具进行签名
java -Djava.library.path=. -jar SignApk.jar platform.x509.pem platform.pk8 webrt-debug.apk out.apk
-
将秘钥转换成keystore,配置在gradle的打包里面进行打包时签名:
-
先用开源脚本
keytool-importkeypair
对秘钥进行转换,依赖openssl,最好在linux上搞sh keytool-importkeypair -k ./platform.keystore -p android -pk8 platform.pk8 -cert platform.x509.pem -alias platform
-
将获得的keystore放到本地的某个目录,并更改项目的build.gradle文件,新增代码如下
-
signingConfigs { release { File strFile = new File("D:/file/apk签名/platform.keystore") storeFile file(strFile) keyAlias 'platform' keyPassword 'android' storePassword 'android' } debug { File strFile = new File("D:/file/apk签名/platform.keystore") storeFile file(strFile) keyAlias 'platform' keyPassword 'android' storePassword 'android' } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { signingConfig signingConfigs.debug } }
-
Git无法clone
首先要配置proxy:
// 查询配置
git config --global --list
// 配置proxy
git config --global http.proxy http://username:password@proxyhk.huawei.com:8080
git config --global https.proxy https://username:password@proxyhk.huawei.com:8080
中途配置完proxy后报错:
fatal: unable to access 'https://aosp.tuna.tsinghua.edu.cn/platform/packages/apps/Car/Settings/': error:1408F10B:SSL routines:ssl3_get_record:wrong version number
一直没找到原因,后来发现是开始配置http.proxy的时候,后面的网址用了https。
Gradle导入项目报错
报错The versions of the Android Gradle plugin and Gradle are not compatible。
解决:修改distributionUrl为https://services.gradle.org/distributions/gradle-3.5.1-all.zip
Gradle新建项目没有src目录
在build.gradle里面添加下面的任务,并执行,然后删掉就好。
task createDirs(){
sourceSets*.java.srcDirs*.each{it.mkdirs()}
sourceSets*.resources.srcDirs*.each{it.mkdirs()}
}
GradleProxy的神坑
我们使用gradle配置proxy总是会抄到maven的配置,但是要注意,gradle在配置noProxyHosts的时候,分隔符是|,而不是maven的逗号,这个巨坑。
MVC–>MVP–>MVVM
本质上,我们的架构只需要有数据模型Model和展示View,但是怎么把两者联系在一起,衍生了各种架构,从MVC中的C告诉V怎么显示,到MVP中的P告诉V做什么V自己去考虑怎么显示,到MVVM中的V自己去VM中取自己要的东西来显示,整个发展就是把M和V分的越来越清晰,M和V粘合的越来越松。
https://academy.realm.io/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/
MVC到MVP的进化,就是把C(以前是activity)拆分成presenter和activity,然后把activity放到V层,这样以后告诉activity就不需要告诉activity怎么做,而是告诉他做什么。
MVP中的activity和presenter抽象成接口并不是必须的,因为他们不一定会有很多种实现,之所以抽象成接口,就是为了方便替换,进行测试。
MVP中的presenter和activity通信,不要穿Button这种和View层其实是强耦合的东西。
MVVM就相当于把以前MVP中P传递给V的展示内容以及V本身的展示控制逻辑都给干掉了,所有的数据反馈你只要在viewModel里面自己存着就行,我view会自己去拿,想当于去掉了以前的数据反馈操作,此时的activity虽然还是在View层,相比于MVP,别说怎么做,连做什么都不需要知道了,只剩下初始化一下资源,绑定一下xml和viewModel的作用了(此时View和Model分离的更彻底了,单元测试也更好搞了)。但自然而然,有的数据你不只是有绑定,你还有逻辑判断,这些判断你就要写到xml里面去了,相当于view和action都是在xml里面搞定了(所以更要注意,不要往xml里面写太多计算和逻辑,不然你回头测试测试不了,尽量直接从viewModel里取结果)。
MVVM相对于MVP好像少了很多繁琐的interface和显示的操作,但是可能很多逻辑都要从java代码中移到xml里面,理想很美好,但是在语法能力以及功能性上,可能会有一些坑。而且你切别的平台也不好切。
android的dataBind
在进行数据绑定的时候,我们会应用绑定的variable对象里面的ObeservableXXX的字段(也是一个对象),这些个数据如果想重置需要注意,不能直接把字段重新指向另一个新new的对象,这样会导致xml里面绑定的还是原来的对象。猜测是因为xml绑定对象后,并不会时时扫描ObeservableXXX字段的指向是否发生变化。
增强For循环在处理数组时的坑
像数组Object[] list = new Object[3]
这样初始化的数组,可以用增强for循环遍历到3个null,但是你像下面这么使用的话,最后的数组里面还是3个null。猜测是因为这个null本身就没有地址,你没法更换实际对象的指向。
Object[] list = new Object[3]
for( Object ob : list ){
ob = new Object()
}
正确的做法是使用普通for循环手动设置规定位置上的值
Object[] list = new Object[3]
for( int i =0; i<list.length; i++ ){
list[i] = new Object()
}
Git
git的常规操作时,先在自己本地clone代码,然后这个master分枝上一直不要写自己的代码(不然你回头不好拉新的分枝,因为git好像没有命令能够直接从远程拉下已经拉过的分支的代码,除非你先checkout某个分支,然后reset到很早,再pull),然后切换到另一个分枝,写完代码之后commit,然后git rebase master
后,再push。
如果你有几个commit要合并成一个,可以用git rebase -i
来手动合并(注意此时应该留最早的一次提交为pick,其他为squash,而且最早的那次提交是在最上面,不是在最下面,还没有时间显示,很坑),或者在commit的时候就直接使用git commit --amend
来合并入上一次的commit
查看远程仓库地址 git remote -v
更新所有远程分支 git remote update
查看所有分支 git remote -a
查看所有本地分支以及他们追踪的远程分支 git remote -vv
处理游离分支,游离分支是checkout了一个commitId后系统自己生成的一个匿名分支。表现为HEAD detached at xxx。
git add --all
git stash save 'comment'
git checkout xxxbranch
git stash list
#pop会删除stash,apply使用完不删除stash,不指定时使用最新的一次stash
git pull
git stash pop/apply [xxx]
与硬件通信的BUG
与硬件进行交互时一定要注意各种bug,这次在控制氛围灯时,由于板子的串口问题,导致如果在5ms内连续发多条指令,会存在指令丢失的问题。
Android错误
现象:Couldn’t read row 0, col -1 from CursorWindow.
原因:因为往SQLite数据库里面新加字段,但是没有更新数据库表,这样从数据库里查询新字段时,其实数据库里不仅是没有数据,还是没有表名的,所以报的这个错误。
功能分析
功能分析需要按层次打开:
- 第0层:考虑当前系统跟其他系统的交互关系,一般就是画一个产品架构图,然后把交互的地方重点圈出来。
- 第1层:将自己的系统打开,按子模块进行分解,不过具体的实现最好是按照AR来进行,因为这样你才能方便别人拿到一个AR,就直接找到实现的方式,而不用通读整个文档,而不是只考虑应用层或者service层,此处的第一层是按照AR(一般是一个子系统功能)把整个链路都打通。
- 第2层:具体到每个AR里面的功能代码的具体实现。
所有的JAVA都是在拼命令行
可以通过修改启动参数来进行JAVA的远程监听DEBUG。有两个关键点:
- 找到启动JVM
- 找到源代码
Scanner使用技巧
scanner的读入会一直挂起程序,等待输入的进入。
如果你要循环不断读入,那你的整个程序都要包在while(scanner.hasnext())里面。
如果你只想读入一遍数据,那就需要if(scanner.hasnext())。
scanner如果要使用readline,则一定会一直等待直到检测到一个\n,如果最后一行没有输入回车,就会一直不结束。不过一般OJ的最后一行还是会输入一个回车符的
JAVA数组的坑
一定要区分好Integer[]和int[],我们经常在使用数组时会使用包装类型,但是这样有一个问题,就是包装类型的每一个初始值是null,你能直接用Integer[i]+=10这种操作,应为null是不能做算术运算的。
也就是说你要是想用Integer[],你最好每一个元素都手动初始化一下,要不你就使用int[],这样每个元素至少会初始化为0,不会出现语法错误。
RC类垃圾回收语言中,weakRef和unownedRef的区别。
weak是说,和强引用一样,也会影响引用计数,但是在GC的时候,如果只有一个weak引用,则对象会被回收掉。多用在A同过字段m引用B对象,但是B是可有可无的,那我就把m字段设置成weak引用,这样B对象在垃圾回收的时候,是可能被回收的。这样既保证了B可以被回收,又保证了B能缓存一段时间。
unowned是说,这个引用不影响引用计数。这个和weak的用法就有点不一样了,不能像上面那样,当A通过m引用B,然后m设置成unowned,这样的话引用B的唯一一个引用就成了unowned,导致m可能永远是null,B一出现就被回收了。正确的做法是A通过m引用B时,如果B里面也有一个字段n引用A,把n设置成unowned,意思就是说A可以引用B,但是B不能引用A。比如父节点可以引用子节点,但是子不能引用父节点(虽然还可以用父节点,但是引用是unowned,不产生引用计数),这样当父节点不再被别人需要时,不会因为子节点持有了父节点的引用,导致因为环引用的问题,大家都不能够被回收。
REPO
彻底同步服务器代码,连续执行以下代码
$ repo sync -d
# Remove all working directory (and staged) changes.
$ repo forall -c 'git reset --hard'
# Clean untracked files
$ repo forall -c 'git clean -f -d'
TMUX快捷键
#列出、关闭会话
tmux ls
tmux kill-session
#进入一个会话
tmux at
#列出、关闭窗口
ctrl-b w
ctrl-b &
#创建、删除一个pane
ctrl-b %
ctrl-b x
除法和取模的真谛
虽然我们一般用除法来获得进位,用取模来获得余数,但是这两个东西本质上是不相关联的。参考leetcode 168题Excel表列名称。
除法和取模都是一个循环一个循环的走,除法算走了多少个循环,取模算绕着循环走下来,走到哪个数。
除法的本质是看看被除数是除数的多少倍(这个是没有什么花招可玩的),所以我们得首先确定除数是多少,一般就是看循环的大小,比如十进制的每一位都是0-9一个循环,一共10个数,所以被除数就是10。我们的编程语言中,一般的除法都会去掉小数部分,所以,此时除法的得到的是有多少倍的整数部分(比如10到19除以10都是1,此处是逢9进位),那就有一个问题了,为什么我们一个循环里,一定是以某个数作为进位的选择呢(这个就是玩花招的地方了),像上个例子的十进制就是逢9进位,我们能不能十进制逢5进位呢?因为我们计算这个问题时候用的除法肯定就是十进制的除法,所以我们想要解决这个问题,除非不用编程语言中的除法,要不我们就得换个思路,比如此时我们可以把被除数偏移一下(比如偏移6,(25-6)/10,就是逢6进位,25时是1,26的时候就是2,偏移多少不用背,你搞个例子算算就行了,因为每一个循环都是固定大小的)。
取模的本质是去除了成倍的除数之后,剩下多少(这个也是没有花招可玩的)。和除法类似,比如10%10=0,其本质是我取完一个循环之后我就从头取,那此时这个0应该是表示我要从头来,然后刚好在十进制除法中,0就是一个循环取完之后,停现在的位置,**那我能不能修改取模每一个循环结束之后,此时取模停在的位置呢?**比如此处能不能一个循环结束之后,停在5上呢?和上面的回答一样,你如果想通过设计好的“/”这个符号来做,那就只能通过偏移,比如此处((10-5)%10=5,10和20取模都是5,就是每10个一循环,一循环结束后就是停在5)。
包名的神坑
在配置路径的时候,IDEA在显示com.abc.cdc的包路径的时候,可能是折叠的三个文件夹(com/abc/cdc),也可能是一个文件夹的名字就是加(com.abc.cdc),两者在IDEA上面的显示是一样的。可能导致你一直找不到引不到包的原因。
通过Z的Binder进行CallBack
我们把callBack对象远程传递过去,远程是怎么调用到我们的方法的呢?梳理一下:
连接的方式决定了你去调用哪个远程的binder。
binder内部的方法序号决定了你调用的是哪个方法。
使用binder的核心,是一对一的调用服务。核心就是实现transact的类(Proxy)和实现ontransact的类(Stub)的一一对应连接,连上了之后,就是内部方法的调用了,然后两者类的内部根据方法序号进行方法区分,也就是写在transact和onTransact方法里面。
那我们是什么时候使得proxy和stub连接上的呢?
第一次连接:肯定是通过bindService,但不是bind的时候连上的,而是在onBind里面返回的,然后再在bind端的onConnected里面拿到返回,这才是我们第一次建立了binder的连接。比如我们一般会在onBind里面写return new XXXStub
,这个返回是一个Stub对应的IBinder接口,IBinder就可以理解成是我们的远程对象的句柄,有了它,我在bind端就能通过Proxy包裹这个IBinder然后调用transact和远程的IBinder通信。**因为你们持有的是同一个IBinder,所以才说两端一一对应了。**从这个角度看,我们只要在bind的时候使用接收不同IBinder的Connection,然后在onBind方法里,根据Intent返回不同的IBinder,就能够实现一个bind服务,可以有几个Aidl了(但就是发送端和接收端都需要知道什么时候用哪个aidl,有耦合度)。
在方法内部传递回调:在方法内部,我们能够直接传递接口对象。这个对象本身需要像普通的aidl类一样写Proxy和Stub,因为这两个类还是一一对应的,所以它的方法序号也是独立于调用它的方法的。核心还是看proxy和Stub是什么时候连接上的,这个就归功于使用callBack类的方法了,他们的第一次连接,是在使用他们的方法的transact里发送CallBackStub的IBinder(比如_data.writeStrongBinder(observer.asBinder())
)和onTransact里面接收IBinder(比如_arg0 = StorageObserver.Stub.asInterface(data.readStrongBinder())
),这样callBack的Proxy和stub就对应使用的是同一个IBinder了。(但就是你的asInterface的代码,要写在别人的onTransact方法里面,两个类就耦合了)
其他事项:
一、首先可以确定的是,下面的Stub的构造方法的Descriptor不会影响远程调用的服务发现,因为我把这个改成一个乱码,也不影响我的远程调用。原因我们也可以分析出来,因为联通是通过onbind或者在transact方法的时候发送,一对一的找到对方,然后发送IBinder。
public StorageObserverStub()
{ super(IStorageObserver.DESCRIPTOR);}
BindService的坑
bindService的回调是异步的,所以你bindService直接拿到一个true,此时的onServiceConnected并没有被回调。所以你不能直接使用里面设置的对象,会有空指针的可能。这个好理解。
但是!!!你不能直接通过Thread.sleep去等回调来,但是却可以通过while死循环判断来等回调。想不明白,这两者不都是阻塞线程吗?不应该是结果都一样吗?
据说回调也是在主线程接收消息进行处理(从日志里看执行onServiceConnected方法的线程和主线程确实是一个,说明回调最后是发到主线程执行的),所以应该是两者都阻塞了主线程,然后两者都拿不到回调吗?但是不知道为啥while可以拿到。
而且此时拿到的日志也好奇怪,日志是先执行了方法,获取了一个我们不期望的方法的返回值(期望50,但日志拿到的是0),然后再执行的回调。竟然没有报空指针。
click getting buton
// bindService成功
connect ability outcometrue
// 使用回调进行方法调用(此处有问题,一个是为什么使用回调是在拿到回调之前,另一个是方法的返回值不应该是0,而应该是其他值,比如50)
get brightness: 0
// 回调成功,不知道为什么打印的日志里面有[xxxx,xx,xx]这种东西
[8924c187f8789f5, 157ac24, f3877] onCreate Shell Service
[8924c187f8789f5, 157ac24, f3877] onStart Z Service
[8924c187f8789f5, 37c0ea8, 3cae04a] is onConnect, code:
[8924c187f8789f5, 853395, 1a1d0e5] connect success
猜测原因:代码发生了重排,使得先执行了回调,然后此时通过回调拿到的对象执行的方法先构造了默认值0.
建议:不要通过阻塞的方式等待回调,直接把你需要等待回调构造对象调用方法等一条龙都写在onServiceConnected里面。每个不同的方法都写一个单独的Connection
使用命令行传递指令给程序的方法
一、使用启动activity的方式,并在启动项参数里带上extra的参数,如下
adb shell am start -n "com.huawei.systemsetting/.MainAbilityShellActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -e method abdfvsd
二、使用app_process启动一个单独的java进程。
app_process
参考https://haruue.moe/blog/2017/08/30/call-privileged-api-with-app-process/
可以使用app_process启动一个main方法,就是启动一个普通的dex程序,没有context: adb shell CLASSPATH=$(echo /data/app/com.example.helloworldcli-*/base.apk) exec app_process /system/bin com.example.helloworldcli.Console --help
删除文件
我们可以用File.delete删除一个文件,但是你重新创建一个同名的新的文件的时候,你不能用上一次用的对象去再次执行删除方法,因为这两个文件的名字虽然一样,但是对应的文件本质可能不同,你得重新new File一下。
Android里面获取系统内部R资源的方式
由于internal.R在sdk包里没有,所以你直接引用这个类是编译不过的。可以采用以下方式在运行时动态获取。
int identifier = Resources.getSystem().getIdentifier("config_screenBrightnessSettingMinimum", "integer", "android");
int integer = getResources().getInteger(identifier);
Android安装系统app
安装系统app是把app放到/system/app/目录下面,然后重启手机,就会自动安装了。
试了一下, 这么安装的系统APP,没有办法通过install再替换掉它。所以想要频繁测试,必须先把/system/app/目录下的安装包删掉,然后重启后,系统APP卸载了, 然后再用install安装。
Android中的Handler的坑
巨坑。Handler如果使用无参的构造方法,必须是在主线程上,不然无法构造。但是不会体现在代码的静态检查上,只有运行时才会体现出来。更坑的是,运行时体现出来了,还不会抛异常,还可以继续运行,导致非常难以定位。
同时还有一个衍生问题:你去bindservice的时候,什么时候service是主线程,什么时候不是。
重构需要注意的问题
想清楚几个问题:
你重构是为了解决什么问题?综合原则是solid+迪米特,可以方便别人阅读、方便修改、隔离变化点。
你重构的代码方便测试吗?需要很好的方法级隔离以及依赖注入设置点,才能方便测试。
你重构的代码考虑了以后的变化点吗?考虑可能的设计模式。
常见的小技巧:
开发初期,字段一般都比较少,可以直接写在类里,当字段越来越多的时候,就可以考虑是否需要封装数据类了。
尽量少使用临时变量,要使用的话,尽量加上final,保证让阅读人不需要去上下文里寻找临时变量的可能变化点。
字段的public一定要慎重,实在要开放给别的类使用,尽量使用get、set字段,保证当别人更改时,至少有个日志记录。
一个方法是否放在一个类里面,需要考虑该方法和该方法依赖的字段是否主要在当前类,以及是否应该在当前类(看情况可以拆分出另一个类)。