个人简历
详解设备ID
详解设备ID
Google-Android-PFD
Symbols count in article: 368 Reading time ≈ 1 mins.
| 模块下载选项 | 与条件分发的兼容性 |
|:————- |:—————:| :————-| :————-|
| 融合 (<dist:fusing dist:include=”true”/>) | 如果某个模块将此选项设为 true,Google Play 在将您的应用部署到搭载 API 级别 19 或更低版本的设备时,不会遵循您指定的按条件分发选项。也就是说,对于搭载 API 级别 19 或更低版本的设备,启用了融合选项的功能模块始终会在安装应用时下载。 |
| 支持免安装体验 (<dist:module dist:instant=”true”/>)| 支持免安装体验的功能模块不支持按条件分发选项。 | |ENGameResource,install_time|
| 按需 (dist:on-demand/) | 默认情况下,如果您指定按条件分发选项,则模块也可按需提供。 |
linux-mmap
Symbols count in article: 0 Reading time ≈ 1 mins.
聚合SDK-设计
Symbols count in article: 1.2k Reading time ≈ 1 mins.
聚合工程
1.用作客户端接入部分的统一框架 SDK_Client
2.用作服务端统一的逻辑转发和处理中心 SDK_Server
3.用作打包功能的逻辑和多线程的任务调度 SDK_Package
4.用户可视化操作界面和功能配置界面 SDK_Manager
SDK_Package
1、打包工具的输入,就是需要打包的 apk 包,也叫母包。游戏里面引入 sdk 抽象层的 jar 包,调用抽象层的接口。完成接入,然后打成 apk。
2、打包工具会首先用 apktool -d 对母包进行反编译。反编译到该渠道对应的临时工作目录中
3、重命名包名。我们知道在 AndroidManifet.xml 中的 package 就是包名,为了防止后面我们重新生成 R 文件导致冲突,我们对每个渠道都设置一个后缀。比如 UC,后缀叫.uc。当乐,后缀.dl 等等。如果渠道 SDK 有明确要求,使用他们提供的后缀,那就使用他们提供的后缀。如果没有,就自己设置一个。
4、拷贝 SDK 资源。将该渠道对应的 SDK 资源,从对应的 SDK 配置目录中,拷贝到反编译后的临时工作目录中。
1)拷贝 SDK 的 assets 目录,libs 目录,res 目录等
2)将 classes.dex 也反编译成 smali 格式,拷贝到反编译临时工作目录中
3)和 SDK_Manifest.xml 中的内容合并到游戏目录的 AndroidManifest.xml 中
5、生成游戏中需要使用的配置。
1)对于之前 SDK 目录中 config.xml 中配置的 APPID,APPKEY 等信息,根据需要,对于需要添加到 AndroidManifest.xml 中的,我们就将他添加到 AndroidManifest.xml 中的 meta-data 中。对于其他参数,我们会在 assets 目录下生成一个 develop_config.properties 文件。
2)对于 SDK 目录下 config.xml 中配置的插件信息,我们会在 assets 目录下,生成一个 plugin_info.xml 文件。这样 SDK 抽象层会读取这个配置来实例化对应的插件。
6、重新生成 R 文件。部分渠道需要支持他们的闪屏画面。根据我们的闪屏解决方案(后面会专门来说),我们需要重新生成 R 文件,来索引我们的闪屏资源图片等信息。
7、重新打包,采用 apktool -b 重新将合并之后的资源和代码,进行打包
8、签名和优化。部分渠道要求使用他们提供的签名文件,所以,我们对签名文件也采用了配置。可以根据不同的渠道来配置不同的签名文件。
9、经过以上步骤,一个渠道包就生成了
渠道配置
静态检测
动态检测
渠道包配置
1.闪屏,ICON
2.日志系统
3.参数
4.渠道sdk版本
5.包名
6.签名方式 v1,v2
7.热更 插件化 需要区分国区/海外
8.可选配置
1 | 可选配置--第三方组件 |
Android-Jni
C++ && Java
Android-OOM
OOM痛点
OOM(Out Of Memory)成为奔溃统计平台上的疑难杂症之一,大部分业务开发人员对于线上OOM问题一般都是暂不处理,一方面是因为OOM问题没有足够的log,无法在短期内分析解决,另一方面可能是忙于业务迭代、身心疲惫,没有精力去研究OOM的解决方案。
作者:蓝师傅
链接:https://juejin.cn/post/7074762489736478757
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
OOM问题分类
-OOM可以大致归为以下3类:
- 线程数太多
- 打开太多文件
- 内存不足
线程数太多
查看系统对每个进程的线程数限制
cat /proc/sys/kernel/threads-max
不同设备的threads-max限制是不一样的,有些厂商的低端机型threads-max比较小,容易出现此类OOM问题
root手机查看
模拟器 部分手机需要root,如小米等硬核
查看当前进程运行的线程数
cat proc/{pid}/status
当线程数超过/proc/sys/kernel/threads-max中规定的上限时就会触发OOM。
真机 ulimit限制
查看当前总进程限制的线程数
ulimit -a
1 | 1|rubens:/ $ ulimit -a |
参数解析
max memory size - 最大内存限制,在64位系统上通常都设置成unlimited
max user processes - 每用户总的最大进程数(包括线程) 总的!!!
virtual memory - 虚拟内存限制,在64位系统上通常都设置成unlimited
既然系统对每个进程的线程数有限制,那么解决这个问题的关键就是尽可能降低线程数的峰值。
查看当前进程运行的线程数
cat proc/{Pid}/status
1 | rubens:/ $ cat proc/18425/status |
参数解析
当前Threads: 27
命令-扩展
ps -ef看到的是进程列表
线程可以通过ps -eLf来查看
根据包名查看当前进程
adb shell ps | grep xxx
1 | u0_a376 22592{PID} 825 6628656 169584 0 0 S com.study.pingindemo |
得到当前进程pid或名字则查看当前所有的线程
adb shell ps -T | grep {PID}
1 | u0_a377 26204 26204 825 6804816 189004 0 0 S tudy.pingindemo |
java
在 Android 中,可以使用如下代码来获取当前线程数:
int threadCount = Thread.activeCount();
activeCount() 方法返回当前活动线程的数量,也就是当前线程数。
注意:activeCount() 方法只能获取到在 Java 虚拟机中创建的线程数,无法获取到其他线程(例如 C++ 创建的线程)的数量。
排查
我们并不清楚到底是哪个部分有问题导致的线程数的增长,所以我们需要一个每1s可以打印一下当前的线程数再通过页面交互来确定到底是哪里出现的问题,可以使用watch命令来完成我们的想法,如下所示:
watch -n 1 -d ‘adb shell ps -T | grep u0_a589 | wc -l’
watch -n 1 -d ‘adb shell ps -T | grep u0_a377 | wc -l’
操作APP时可以试试的看到线程数的大小,并且通过观察看到那类的线程名字在增多.
线程优化
禁用 new Thread
不过这种方式存在两个问题:
无法解决老代码的new Thread;
对于第三方库无法控制。
无侵入性的new Thread 优化
创建一个Thread的子类叫ShadowThread吧,重写start方法,调用自定义的线程池CustomThreadPool来执行任务
1 | public class ShadowThread extends Thread { |
在编译期,hook 所有new Thread字节码,全部替换成我们自定义的ShadowThread,这个难度应该不大,按部就班,
我们先确认new Thread和new ShadowThread对应字节码差异,可以安装一个ASM Bytecode Viewer插件,如下所示
由于将任务放到线程池去执行,假如线程奔溃了,我们不知道是哪个线程出问题,所以自定义ShadowThread中的内部类MyRunnable 的作用是:在线程出现异常的时候,将异常捕获,还原它的名字,重新抛出一个信息更全的异常。
分析线程词
1 | public ThreadPoolExecutor(int corePoolSize, |
1.corePoolSize:核心线程数量。核心线程默认情况下即使空闲也不会释放,除非设置allowCoreThreadTimeOut为true。
2.maximumPoolSize:最大线程数量。任务数量超过核心线程数,就会将任务放到队列中,队列满了,就会启动非核心线程执行任务,线程数超过这个限制就会走拒绝策略;
3.keepAliveTime:空闲线程存活时间
4.unit:时间单位
5.workQueue:队列。任务数量超过核心线程数,就会将任务放到这个队列中,直到队列满,就开启新线程,执行队列第一个任务。
6.threadFactory:线程工厂。实现new Thread方法创建线程
线程泄露
1.主要监控native线程的几个生命周期方法:pthread_create、pthread_detach、pthread_join、pthread_exit
2.hook 以上几个方法,用于记录线程的生命周期和堆栈,名称等信息;
3.当发现一个joinable的线程在没有detach或者join的情况下,执行了pthread_exit,则记录下泄露线程信息;
4.在合适的时机,上报线程泄露信息
3 打开太多文件
Linux 系统一切皆文件,进程每打开一个文件就会产生一个文件描述符fd(记录在/proc/pid/fd下面)
cd /proc/10654/fd
ls
Android-Activity启动过程
代理方式
1.代理方式插件化实现
未安装的 APK文件是可以通过 DexClassLoader 来加载、运行的
1 | private DexClassLoader createDexClassloader(String apkPath) { |
其中 :
1、mContext 是一个 Context类型的对象。
2、apkPath 就是 apk 在手机中的绝对路径(示例中是从 assets 中复制到手机中的)。 3、file.getAbsolutePath() 就是 APK 解压出的 dex 的存放目录。
这里返回的 loader 就是一个可以加载 APK 的 ClassLoader。然后通过 ClassLoader 加载这个 APK 的默认启动的 Activity,并且通过反射调用这个插件 Activity 的 onCreate,onStart,onResume 函数就可以加载这个应用。
资源加载
资源的加载需要 Resouces ,而创建 Resouces 需要 AssetManager,因此,先创建 AssetManager,再创建 Resouces.
1 | private AssetManager createAssetManager(String apkPath) { |
2.Hook方式实现
Hook目的是替换掉系统默认逻辑
Activity 启动过程
插件化支持首先要解决的一点就是插件里的 Activity 并未在宿主程序的 AndroidMainfest.xml 注册。常规方法肯定无法直接启动插件的 Activity,这个时候就需要去了解 Activity 的启动流程。
流程
Activity 调用 startActivity,实际会调用 Instrumentation 类的 execStartActivity 方法,
Instrumentation 是系统用来监控 Activity 运行的一个类,
1.Activity 调用 startActivity,实际会调用 Instrumentation 类的 execStartActivity 方法,Instrumentation 是系统用来监控 Activity 运行的一个类,Activity 的整个生命周期都有它的影子。
2.通过跨进程的 Binder 调用,进入到 ActivityManagerService 中,其内部会处理 Activity 栈。之后又通过跨进程调用进入到需要调用的 Activity 所在的进程中。
3.ApplicationThread 是一个 Binder 对象,其运行在 Binder 线程池中,内部包含一个 H 类,该类继承于类 Handler。ApplicationThread 将启动需要调用的 Activity 的信息通过 H 对象发送给主线程。
4.主线程拿到需要调用的 Activity 的信息后,调用 Instrumentation 类的 newActivity 方法,其内通过 ClassLoader 创建 Activity 实例。
SDK-Server
Symbols count in article: 2k Reading time ≈ 2 mins.
sdkserver
聚合sdk
SDK接入主要是接第三方平台的登录和支付流程,以手机游戏为例,第三方平台就是国内较大的游戏中心和手机应用商店。
游戏商开发手机游戏的流程:
1、开发完游戏逻辑
2、接入第三方SDK渠道的登录和支付,并且和第三方联调完成后打包客户端——
3、客户端上传到第三方游戏平台
4、第三方游戏平台审核通过后上架到游戏中心或者手机应用商店供玩家下载——
5、玩家安装客户端后采用滚服的方式来分散玩家,同时每个服每周更新新的活动
6、玩家支付,会根据手机的渠道类型调用对应的第三方SDK支付工具进行支付,然后返回支付信息给游戏商的支付服确认支付信息,游戏商支付服确认支付信息完成后调用游戏服发放充值道具
注意:
1、客户端一般都是定义一个抽象的SDK接入接口,然后不同渠道的SDK接入到客户端时都实现这个接口。
2、目前出现较多的专门做SDK接入的公司和机构,这种统一的SDK接入渠道有:棱镜sdk,AnySDK,易接,U8SDK(第三方渠道SDK接入框架)。但是统一的SDK接入渠道并不能支持所有的SDK接入,所有不支持的SDK还是需要游戏商自己去接入。
3、统一的SDK接入框架相当于是将游戏客户端接入到一个代理SDK接入的工具上,这个工具会自动帮你登录和支付的接入,从而简化流程。
4、但是带来的问题就是游戏商的登录和支付玩家数据要走代理SDK接入的工具,这样就会让除游戏平台和游戏商之外的专门做SDK接入代理的公司掌握你的游戏玩家数据。棱镜sdk,AnySDK,易接三大公司都是这样的模式。 但是不一样的是U8SDK,U8SDK是代码完全开源的,也就是说游戏商可以下载到这个SDK接入代理工具的所有源码,在自己的游戏商本地搭建一个U8SDK服务器(U8Server渠道SDK统一用户登录认证中心和支付中心),这样数据走的就是自己的U8SDK服务器,不会担心泄露的问题。
1 | 手机游戏的登录过程: |
游戏SDK解释:
手机游戏中sdk接入简单理解就是用他们的平台登录,比如91sdk是91助手平台,360sdk是360平台。
软件开发工具包广义上指辅助开发某一类软件的相关文档、范例和工具的集合。
软件开发工具包是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等创建应用软件的开发工具的集合,一般而言SDK即开发 Windows 平台下的应用程序所使用的 SDK。它可以简单的为某个程序设计语言提供应用程序接口 API 的一些文件,但也可能包括能与某种嵌入式系统通讯的复杂的硬件。一般的工具包括用于调试和其他用途的实用工具。SDK 还经常包括示例代码、支持性的技术注解或者其他的为基本参考资料澄清疑点的支持文档。
为了鼓励开发者使用其系统或者语言,许多 SDK 是免费提供的。软件工程师通常从目标系统开发者那里获得软件开发包,也可以直接从互联网下载,有时也被作为营销手段。例如,营销公司会免费提供构建SDK 以鼓励人们使用它,从而会吸引更多人由于能免费为其编程而购买其构件。
SDK 可能附带了使其不能在不兼容的许可证下开发软件的许可证。例如产品供应商提供一个专有的 SDK 可能与自由软件开发抵触。GPL 能使 SDK 与专有软件开发近乎不兼容。LGPL 下的 SDK 则没有这个问题。
逆向基础-Android
Symbols count in article: 1.1k Reading time ≈ 1 mins.
移动端
Android
核心技术
检测
静态检测
动态检测
Java代码保护:
1.JAR文件加壳
2.JAR文件代码抽离
3.JAR文件代码虚拟化。
SO文件保护:
1.SO加壳。
2.SO Linker。
3.SO防调用。
4.SO VMP。
SO处理思路
1 | 1.SO加壳 |
防Java层调试
防止集成SDK时通过IDE(Eclipse/Android Studio等)调试SDK的Java代码,防止逆向调试Dex中的smali代码。
防调用
防止SDK被非授权的第三方应用进行非法集成调用。
防native层调试
通过对进程状态、端口、信号的实时监听探测保护SDK不被native层动态调试。
so科普篇
so文件本质上也是一种ELF文件。
ELF(Executable and Linkable Format)文件是一种常见的二进制文件格式,用于在类Unix系统中表示可执行程序、共享库、目标文件和其他可加载的二进制文件。它是一种灵活的文件格式,广泛用于Linux、Unix和类Unix操作系统,以及一些嵌入式系统。
1 | // 查看手机进程 |