Android之 知识总结第二篇
一, Gradle 、AGP(Android Gradle Plugin)、 buildTools分别是什么,他们之间什么关系?
- Gradle
Gradle是基于JVM的构建工具。他本身使用jave写的,gradle的脚本也就是build.gradle通常是用groovy语言。- Android BuildTools
Android SDK Build-Tools 是构建 Android apk、AAR文件等Android平台产物所需的一个 Android SDK 组件,安装在 /build-tools/ 目录下。
├── ⋮
├── aapt2
├── d8
├── apksigner
├── zipalign
├── ⋮
- AAPT2*:Android 资源打包工具)是一种构建工具,Android Studio 和 Android Gradle 插件使用它来编译和打包应用的。AAPT2 会解析资源、为资源编制索引,并将资源编译为针对 Android 平台进行过优化的二进制格式。
- apksigner:工具为 APK 签名,并确保 APK 的签名将在该 APK 支持的所有版本 Android 平台上成功通过验证。
- d8:Android Gradle 插件使用该工具来将项目的 Java 字节码编译为在 Android 设备上运行的 DEX 字节码。
- Zipalign:在将 APK 文件分发给最终用户之前,先使用 zipalign 进行优化。
二,在rootProject下的build.gradle中,buildscript的
repositories和allprojects的repositories有什么区别?
buildscript {
repositories {
jcenter()
google()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
allprojects {
repositories {
jcenter()
google()
maven {
url "http://maven.xxxxxxxx/xxxxx"
}
}
}
• buildscript里是gradle脚本执行所需依赖,如上所示对应的是maven库和插件 。
• allprojects里是项目本身需要的依赖,比如代码中某个类是打包到maven私有库中的,那么在allprojects—>repositories中需要配置maven私有库,而不是buildscript中,不然找不到。
三,Kotlin携程 Dispatchers.Default和Dispatchers.IO的区别
- Dispatchers.Default 提交任务,此时线程池里所有任务都在忙碌,于是尝试创建新的线程,而又因为当前计算型的线程数=8,等于核心线程数,此时不能创建新的线程,因此该任务暂时无法被线程执行。
- Dispatchers.IO提交任务,此时线程池里所有任务都在忙碌,于是尝试创建新的线程,而当前阻塞的任务数为1,当前线程池所有线程个数为8,因此计算型的线程数为 8-1=7,小于核心线程数,最后可以创建新的线程用以执行任务。
- 这也是两者的最大差异,因为对于计算型(非阻塞)的任务,很占CPU,即使分配再多的线程,CPU没有空闲去执行这些线程也是白搭,而对于IO型(阻塞)的任务,不怎么占CPU,因此可以多开几个线程充分利用CPU性能。
四,App抓包和防抓包
使用Charles抓包需要做哪些操作:
- 电脑上需要安装证书。这个主要是让Charles充当中间人,颁布自己的CA证书。
- 手机上需要安装证书。这个是访问Charles获取手机证书,然后安装即可。
- Android项目代码设置兼容。Google 推出更加严格的安全机制,应用默认不信任用户证书(手机里自己安装证书),自己的app可以通过配置解决,相当于信任证书的一种操作!
防抓包的关键点:
- APP安全配置
- 添加配置文件:
android:networkSecurityConfig=“@xml/network_security_config”- 需要给okhttpClient配置:
用X509TrustManager 来监听校验服务端证书有效性。遍历设备上信任的证书,通过证书别名将用户证书(别名中含有user字段)过滤掉,只将系统证书添加到验证列表中
- 关闭代理
- charles 和 fiddler 都使用代理来进行抓包,对网络客户端使用无代理模式即可防止抓包,如:
OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY)
.build()- 通常情况下上述的办法有用,但是无法防住使用 VPN 导流进行的抓包:
使用VPN抓包的原理是,先将手机请求导到VPN,再对VPN的网络进行Charles的代理,绕过了对App的代理。
- 证书校验(单向认证)
- 下载服务器端公钥证书:
为了防止上面方案可能导致的“中间人攻击”,可以下载服务器端公钥证书,然后将公钥证书编译到Android应用中一般在assets文件夹保存,由应用在交互过程中去验证证书的合法性。- 设置证书校验:
通过OkHttp的API方法 sslSocketFactory(sslSocketFactory,trustManager) 设置SSL证书校验。- 设置域名合法性校验:
通过OkHttp的API方法 hostnameVerifier(hostnameVerifier) 设置域名合法性校验。
- APP安全配置双向认证
- 什么叫做双向认证:
SSL/TLS 协议提供了双向认证的功能,即除了 Client 需要校验 Server 的真实性,Server 也需要校验 Client 的真实性。- 双向认证的原理:
双向认证需要 Server 支持,Client 必须内置一套公钥证书 + 私钥。在 SSL/TLS 握手过程中,Server 端会向 Client 端请求证书,Client 端必须将内置的公钥证书发给 Server,Server 验证公钥证书的真实性。
用于双向认证的公钥证书和私钥代表了 Client 端身份,所以其是隐秘的,一般都是用 .p12 或者 .bks 文件 + 密钥进行存放。- 代码层面如何做双向认证:
双向校验就是自定义生成客户端证书,保存在服务端和客户端,当客户端发起请求时在服务端也校验客户端的证书合法性,如果不是可信任的客户端发送的请求,则拒绝响应。服务端根据自身使用语言和网络框架配置相应证书校验机制即可。
- 防止挂载抓包
- 什么是挂载
- Xposed黑科技:
Xposed + JustTrustMe 可以破解绕过校验CA证书。那么这样CA证书的校验就形同虚设了,对App的危险性也很大。- App多开运行在多个环境上:
多开App的原理类似,都是以新进程运行被多开的App,并hook各类系统函数,使被多开的App认为自己是一个正常的App在运行。
一种是从多开App中直接加载被多开的App,如平行空间、VirtualApp等,另一种是让用户新安装一个App,但这个App本质上就是一个壳,用来加载被多开的App。- VirtualApp黑科技:
它破坏了Android 系统本身的隔离措施,可以进行免root hook和其他黑科技操作,你可以用这个做很多在原来APP里做不到事情,于此同时Virtual App的安全威胁也不言而喻。
- 判断是否具有Xposed环境:
- 第一种方式:获取当前设备所有运行的APP,根据安装包名对应用进行检测判断是否有Xposed环境。
- 第二种方式:通过自造异常来检测堆栈信息,判断异常堆栈中是否包含Xposed等字符串。
- 第三种方式:通过ClassLoader检查是否已经加载了XposedBridge类和XposedHelpers类来检测。
- 第四种方式:获取DEX加载列表,判断其中是否包含XposedBridge.jar等字符串。
- 第五种方式:检测Xposed相关文件,通过读取/proc/self/maps文件,查找Xposed相关jar或者so文件来检测。
- 判断是否是双开环境:
- 第一种方式:通过检测app私有目录,多开后的应用路径会包含多开软件的包名。还有一种思路遍历应用列表如果出现同样的包名,则被认为双开了。
- 第二种方式:如果同一uid下有两个进程对应的包名,在"/data/data"下有两个私有目录,则该应用被多开了。
- 判断了具有xposed或者多开环境怎么处理App:
- 目前使用VirtualApp挂载,或者Xposed黑科技去hook,前期可以先用埋点统计。测试学而思App发现挂载在VA上是推出App。
- 数据加解密
- 第一步:获取请求的数据。主要是获取请求url和requestBody,这一块需要对数据一块处理。
- 第二步:对请求数据进行加密。采用RC4加密数据。
- 第三步:根据不同的请求方式构造新的request。使用 key 和 result 生成新的 RequestBody 发起网络请求。
- 数据加解密证书锁定
- 证书锁定是Google官方比较推荐的一种校验方式:
原理是在客户端中预先设置好证书信息,握手时与服务端返回的证书进行比较,以确保证书的真实性和有效性。- 如何实现证书锁定:
- 一种通过network_security_config.xml配置
第一种方式:配置文件
域名:api.zuoyebang.cnxxx
38JpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhK90=
9k1a0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM90K=- 另一种通过代码设置;
val pinners = CertificatePinner.Builder()
.add(“api.zuoyebang.cnxxx”, “sha256//89KpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRh00L=”)
.add(“api.zuoyebang.cnxxx”, “sha256//a8za0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1o=09”)
.build()
val builder = OkHttpClient.Builder()
builder.apply {
certificatePinner(pinners)
}
- Sign签名:为了保证数据在通信时的安全性,我们可以采用参数签名的方式来进行相关验证。
- 需要对请求参数进行签名验证,签名方式如下:key1=value1&key2=value2&key3=value3&secret=yc 。对这个字符串进行md5一下
- 服务端对sign校验:
合法正确签名sign才可以获取数据。这样就解决了身份验证和防止参数篡改问题,如果请求参数被人拿走,没事,他们永远也拿不到secret,因为secret是不传递的。再也无法伪造合法的请求
五,CPU 和 ABI 的关系
CPU 架构是 CPU 厂商定义的 CPU 规范,目前主流的 CPU 架构分为两大类:
- 复杂指令集(CISC): 例如 Intel 和 AMD 的 X86 架构;
- 精简指令集(RISC): 例如 IBM 的 PowerPC 架构、ARM 的 ARM 架构。
Android 支持 的 ABI
ABI | 描述 |
---|---|
armeabi | 第 5 代、第 6 代的ARM 处理器,基本退出历史舞台 |
armeabiv-v7a | 第 7 代及以上的 ARM 处理器,正在逐步退出历史舞台 |
arm64-v8a | 第 8 代、64 位 ARM 处理器,目前是主流 |
x86 / x86_64 | 一般是模拟器 |
Android构建64位apk:ndk.abiFilters 配置
android {
...
defaultConfig {
...
ndk {
abiFilters "armeabi-v7a","arm64-v8a"
}
}
}
六,你不一定知道的Android小知识
- 子线程未必不能更新UI
- Android的UI访问是没有加锁的,多线程访问时并不安全。所以规定只能在UI线程中访问UI。
负责检查线程的就是 ViewRootImpl 的 checkThread() 方法:- 然而 ViewRootImpl 的创建在 onResume() 回调之后。那么在 onResume() 之前,子线程里也是可以更新 UI 的 。
- 即使是 ViewRootImpl 创建后,只要不调用 checkThread(),子线程里更新也并不会报错。
- 代码 new 的 View 没有id
- Android布局文件中通过 @+id 的方式,可以在 R文件 中生成对应的一个Int值,用于在运行时保证资源唯一性,但动态在代码中 new 的 View 没有 id 。
- 如果你有需求使用它的id,可以调用 View 的 generateViewId() 方法来生成 id(API17+) ,而非用随机数产生或手写一个具体的值。
- View 的 getContext 返回的未必是Activity
- Activity中setContentView时一定是Activity;
- 通过 new View、View.inflate、LayoutInflater.inflate 这几种方式添加View,我们传参时传的是什么context, View中的就是什么Context.
- 在5.0系统版本以下的手机,且 Activity 是继承自 AppCompatActivity 的,那么View的getConext方法,返回的就不是Activity而是TintContextWrapper。
- boolean 类型占几个字节
- java中 boolean 表示的实际信息是一位:1表示true,0表示false。但是,Java规范 数据类型文档 没有精确定义内存中布尔变量的实际大小。
- 其大小与虚拟机相关,可以肯定的是,不会是 1 个 bit 。
- Java 虚拟机的建议如下:
- 1.boolean 类型被编译成 int 类型来使用,占 4 个 byte 。
- 2.boolean 数组被编译成 byte 数组类型,每个 boolean 数组成员占 1 个 byte
- 硬件加速不是哪里都能开关
- 硬件加速在Window级只能开不能关;View级只能关不能开。
- Application 和 Activity 控制,在 AndroidManifest 文件中 Application 或 Activity 节点添加
android:hardwareAccelerated=“true”- Window控制
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)- View控制
view.setLayerType(View.LAYER_TYPE_SOFTWARE,null);- 查询是否开启硬件加速
View.isHardwareAccelerated()
Canvas.isHardwareAccelerated()
- 用 getVisibility() 判断用户是否能看见并不好
- getVisibility()只判断它自身是否是显示状态。但是如果它的父级不可见呢?
- 用 isShown() 方法更合适些,会先判断当前 View 的flag, 然后循环拿到父View,判断是不是可见。只要有一个是不可见的,就返回false。
七,Fragment
- getActivity()空指针:
这种情况一般发生在在异步任务里调用getActivity(),而Fragment已经onDetach(),此时就会有空指针,解决方案是在Fragment里使用
一个全局变量mActivity,在onAttach()方法里赋值,这样可能会引起内存泄漏,但是异步任务没有停止的情况下本身就已经可能内存泄漏,相比直接crash,这种方式
显得更妥当一些。- Fragment视图重叠:
在类onCreate()的方法加载Fragment,并且没有判断saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),导致重复加载了同一个Fragment导致重叠。(PS:replace情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)
八,SurfaceView
- SurfaceView中包含一个Surface对象,而Surface是可以在后台线程中绘制的,SurfaceView的性质决定了其比较适合一些场景:需要界面迅速更新、对帧率要求较高的情况
- 在绘制线程中必须先合法的获取 Surface 才能开始绘制内容, SurfaceHolder.Callback.surfaceCreated() 和 SurfaceHolder.Callback.surfaceDestroyed() 之间的状态为合法的,另外在Surface类型为SURFACE_TYPE_PUSH_BUFFERS时候是不合法的。 额外的绘制线程会消耗系统的资源,在使用SurfaceView的时候要注意这点
- 只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
- surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘Surface。
- surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
- surfaceDestroyed(SurfaceHolder holder):当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,一般在该函数中来清理使用的资源。
- 通过SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas(Rect dirty)函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。
- 在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
- 当在Canvas中绘制完成后,调用函数unlockCanvasAndPost(Canvas canvas)来通知系统Surface已经绘制完成,这样系统会把绘制完的内容显示出来。为了充分利用不同平台的资源,发挥平台的最优效果可以通SurfaceHolder的setType函数来设置绘制的类型,目前接收如下的参数:
- SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
- SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
- SURFACE_TYPE_GPU:适用于GPU加速的Surface
- SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。
九 kotlin-compiler-embeddable 下载慢的问题
- 打开网址 https://mvnrepository.com/
- 搜索kotlin-compiler-embeddable
- 进入kotlin-compiler-embeddable
- 点入所需jar包,file后面下载所需jar包版本
- 配置下载jar文件到.gradle文件中
- 文件路径:/Users/“用户名”/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-compiler-embeddable/1.3.70/
- 重启AS
十 java判断点在三角形内
- java中,在给定三角形的三个顶点和一个待判断的点的情况下,我们可以使用一些数学知识来判断该点是否在三角形内。
- 首先,我们可以使用向量叉积的方法来进行判断。给定三角形顶点A、B、C,以及待判断的点P。我们可以计算向量AP、BP和CP与向量AB、BC和CA的叉积,分别表示为cross1、cross2和cross3。如果这三个叉积均为正数或者均为负数,那么点P位于三角形ABC内部。如果至少有一个叉积为0,则表示该点在三角形ABC上。
- 其次,我们还可以通过计算三个子三角形的面积之和与原三角形面积进行比较来判断。同样给定三角形顶点A、B、C,以及待判断的点P。我们可以计算出子三角形ABP、BCP和CAP分别的面积,并将它们加起来得到sum_areas。然后再计算出原三角形ABC的面积area。如果sum_areas等于area,则表示该点位于三角形ABC内部;如果sum_areas小于area,则表示该点在三角形ABC外部。
- 最后,在Java中,我们可以编写一个方法来实现上述算法,并返回一个布尔值表示待判断的点是否在给定的三角形内。
public static boolean isPointInTriangle(Point A, Point B, Point C, Point P) {
// 计算向量叉积
double cross1 = (P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x);
double cross2 = (P.x - B.x) * (C.y - B.y) - (P.y - B.y) * (C.x - B.x);
double cross3 = (P.x - C.x) * (A.y - C.y) - (P.y - C.y) * (A.x - C.x);
// 判断是否在三角形内部
if((cross1 > 0 && cross2 > 0 && cross3 > 0) ||
(cross1 < 0 && cross2 < 0 && cross3 < 0)) {
return true;
}
// 判断是否在三角形上
if(cross1 == 0 || cross2 == 0 || cross3 == 0){
return true;
}
return false;
}
十一 RK 开发板源码编译
- RK官网
- https://www.rock-chips.com/
- RK开发工具,AndroidSDK网址
- https://www.t-firefly.com/doc/download/page/id/3.html#other_144
- https://wiki.t-firefly.com/zh_CN/Firefly-RK3399/compile_android8.1_firmware.html
- RK开发板AndroidSDK和AndroidStudio的AndroidSDK区别
- RK开发板AndroidSDK是指为RK系列开发板定制的Android开发工具包,包含了开发板的驱动程序、库文件、示例代码等,以便开发者可以在RK开发板上进行Android应用的开发和调试。
- 而AndroidStudio的AndroidSDK是指Google官方提供的Android开发工具包,包含了Android平台的API、工具和库文件等,用于开发和调试Android应用。
- 两者的区别主要在于适用对象和功能。RK开发板AndroidSDK主要针对RK系列开发板,提供了特定的驱动和示例代码,方便开发者在RK开发板上进行Android应用的开发和调试;而AndroidStudio的>* AndroidSDK适用于所有的Android设备,提供了全面的Android开发工具和资源,可用于开发和调试任何Android设备上的应用。
- 总结来说,RK开发板AndroidSDK是为RK系列开发板定制的Android开发工具包,而AndroidStudio的AndroidSDK是Google官方提供的通用Android开发工具包。
十二 so库冲突解决
- 出现错误的问题日志
2 files found with path 'lib/arm64-v8a/libc++_shared.so' from inputs:
- D:\AndroidProject\juaismapp\opencvsdk\build\intermediates\library_jni\debug\jni\arm64-v8a\libc++_shared.so
- D:\AndroidCache\.gradle\caches\transforms-3\74c11438533af106ed521e9aba5a6698\transformed\jetified-mmkv-1.0.22\jni\arm64-v8a\libc++_shared.so
If you are using jniLibs and CMake IMPORTED targets, see
- 解决:
这是以为opencv和mmkv里面都有 ibc++_shared.so 库,造成冲突,我们只需在build.gradle中的android节点下将这些重复的so文件依次声明优先使用第一个即可
android {
........
packagingOptions {
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
}
}