APK 控件逆向工程(36氪,作业盒子)
0. 前言
本文阅读需要10分钟. 你可能的收获:
- 学会简单的反编译
- 一些反编译技巧:实战提取两个知名应用的控件,借鉴学习
- 学会一种提高工作效率的偷懒方法
- 希望能给读者正在做的项目有点启发,少走点弯路
每一个android开发程序员很有必要掌握一些逆向工程的知识技巧,其中好处不胜枚举,我细数一二:
- 参考竞品开发框架,技术选型.比如这样的分析:超千万注册用户APP技术实现简析-android《洋葱数学》
- 核心功能实现上少走弯路,向竞品学习,取长补短
- 满足自己的好奇心,比如知乎曾有个创意广告,可以源码级别上大概了解到他们如何实现的仿知乎创意广告
1. 反编译效果展示
1.1 《36氪》下拉刷新(APK下载地址)
各位老铁可以下载体验一下这个app的***下拉刷新***,绝对让你很有收获
1.1.1原生效果:
1.1.2《36氪》下拉刷新控件评价
- 非常流畅,能极大缓解用户等待数据时的焦灼感. 这点非常牛,市面上的应用多如牛毛,其中很多应用下拉刷新控件的质量却很堪忧.鄙人以为《36氪》的下拉刷新体验在应用市场上绝对名列前茅.他们的UE交互设计师一定花了很多时间精力设计它.
- 下拉时有缩放效果,等待刷新时填充图标的动效,整个过程行云流水
- 有一种美感在其中.
那么问题来了,假如你的项目中要实现这样一个控件,你需要多长时间? 1天? 2天? 我的答案是1个小时 反编译提取控件,理解其中绘制逻辑.找UI改改样式,over.
1.1.3反编译提取控件效果:
嘻嘻,下面我会给个关于反编译技巧的教程,市面上大多数的应用脱了裤子给我们看,刺激吧?
1.2 《作业盒子-教师版》批注控件(APK下载地址)
1.2.1《作业盒子》批注作业效果
1.1.3《作业盒子》批注作业控件评价
- 编辑模式:同时支持双手缩放,以及单手批注
- 缩放的同时,批注笔头粗细跟随缩放
- 不可编辑模式:支持双手缩放,单手拖拽
- 并且支持批注回退 让我老老实实做,估计最少也得3天,就算实现了,估计还有不少bug需要修复.没错,我就是这么不自信.有点自知之明还是好的. 但是,反编译提取控件,估计也就1,2个小时. 我们项目也需要实现类似功能.一直有个用户需要的需求迟迟没有落地---[图片上的文字看不清楚,需要支持放大功能,但是同时还得支持批注功能] 因为没有想到好的交互实现方案,并且有其他功能要做,做这个需求性价比太低了,开发周期这么紧,最后不了了之.而现在,简直是分分钟的事.
1.2.2《作业盒子》反编译提取控件
2. 《36氪》下拉刷新:
2.1正常情况下,不考虑反编译,自己实现的思路分析:
- 实现跟随用户上拉下拉 蓝色方框的缩放动画(使用值动画)
- 刷新过程中的动画,如果使用自定义view画出来,估计成本会比较高,如果使用lottie会省事很多
- 不过做出来说不定还有bug,使用反编译,这样的担忧会减轻很多,毕竟36kr这个下拉控件经过庞大用户无数次的考验.
2.2通过反编译提取
2.2.1目标:
还原实现下拉刷新控件
2.2.2 会遇到的困难:
①控件位置难找; ②资源文件分散; ③代码经过混淆,代码逻辑需要跟着作者实现思路走一遍
2.2.3 提取过程:
- 提取jar包以及资源文件 使用apktool 或使用反编译集成工具 这一步没啥难度,建议读者想跟着实践一下的话,首选反编译集成工具. 用命令行工具会很麻烦,光是插件的安装就这么多,更别提安装过程的环境问题.
1.ShakaApkTool 2.Apktool 3.Dex2Jar 4.Zipalign 5.SignApk 6.JDGUI
我是直接github上找到一个mac工具软件:android crack tool
傻瓜化操作后获得如下文件: 2.随便新建一个AS项目,将jar包添加到libs 然后add as library 3.在android studio中使用analyse apk 4.定位下拉刷新控件的代码位置 这一步需要耐心,因为不太好找,需要猜一下. a,找到mainActivity b.在mainActivity中找到使用该控件的fragment
c.关键:在fragment中找到控件(需要一点点小耐心) 一开始没找着,想了一下,这个fragment肯定是使用了下拉刷新的,位置没找错. 那问题出现在哪里呢?
推测36Kr的程序员对这个下拉刷新动作进行了封装.可能在BaseFragment中,然而也没有! 同样的,在Rxfragment中也没找着. 回到HomeFragment2中,看能不能找到点蛛丝马迹 果然找到了一点线索:有关于refresh关键字 原来HomeFragment2是另一个fragment的容器,找错位置了,回到MainActivity中找其他fragment 在SubscribeHomeFragment中马上就找到了
in.srain.cube.views.ptr.PtrFrameLayout //第一反应是网上的开源库,github上一搜索,果然~
复制代码
36Kr使用比较出名的下拉刷新库github地址:android-Ultra-Pull-To-Refresh
d.根据下拉刷新头部KrHeader以及资源R文件定位资源文件
根据header_kr这个id去搜索定位布局文件
e. 根据KrHeader的变量LottieAnimationView b找到lottie动画 根据lottie文档官网,动画文件一般放在asserts文件或res/raw中
至此,这个控件已经被完完全全的抽取出来了
2.2.4 源码展示
PS:对混淆代码进行理解后,进行变量以及类名重命名,添加上一些必要注释
头部刷新代码控件()
/*** 郑重声明:本源码均来自互联网,仅供个人欣赏、学习之用,* 版权归36氪产品发行公司所有,任何组织和个人不得公开传播或用于任何商业盈利用途,* 否则一切后果由该组织或个人承担。* 本人不承担任何法律及连带责任!请自觉于下载后24小时内删除**/
public class KrHeader extends FrameLayout implements PtrUIHandler {private ImageView mScaleImageView;private LottieAnimationView mLoadingLottieView;private TextView mRefreshInfoTextView;private boolean isShowRefreshInfo;public KrHeader(Context context) {this(context, (AttributeSet)null);}public KrHeader(Context context, AttributeSet attributeSet) {super(context, attributeSet);this.init(context);}public KrHeader(Context context, AttributeSet attributeSet, int defStyleRes) {super(context, attributeSet, defStyleRes);this.init(context);}@TargetApi(21)public KrHeader(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {super(context, attributeSet, defStyleAttr, defStyleRes);this.init(context);}private void init() {this.mScaleImageView.setVisibility(GONE);this.mLoadingLottieView.setVisibility(VISIBLE);this.mRefreshInfoTextView.setVisibility(GONE);this.mLoadingLottieView.playAnimation();}private void init(Context var1) {View var2 = inflate(var1, R.layout.header_kr, this);this.mScaleImageView = (ImageView)var2.findViewById(R.id.pre);this.mLoadingLottieView = (LottieAnimationView)var2.findViewById(R.id.loading);this.mRefreshInfoTextView = (TextView)var2.findViewById(R.id.tv_refresh_info);}private void onUIRefreshPrepare() {this.mScaleImageView.setVisibility(VISIBLE);this.mLoadingLottieView.setVisibility(GONE);this.mRefreshInfoTextView.setVisibility(GONE);this.mLoadingLottieView.setProgress(0f);this.mLoadingLottieView.cancelAnimation();}private void onUIRefreshComplete() {if (this.isShowRefreshInfo) {this.mScaleImageView.setVisibility(GONE);this.mLoadingLottieView.setVisibility(GONE);this.mRefreshInfoTextView.setVisibility(VISIBLE);}}public TextView getCompleteView() {return this.mRefreshInfoTextView;}/*** 根据手势上下拉缩放imageview* @param frame* @param isUnderTouch* @param status* @param ptrIndicator*/@Overridepublic void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {int offset = frame.getOffsetToRefresh();int currentPosY = ptrIndicator.getCurrentPosY();if (currentPosY >= offset) {this.mScaleImageView.setScaleX(1.0F);this.mScaleImageView.setScaleY(1.0F);} else if (status == 2) {//根据偏移量计算缩放比例float scale = (float)(offset - currentPosY) / (float)offset;this.mScaleImageView.setScaleX(1.0F - scale);this.mScaleImageView.setScaleY(1.0F - scale);}}@Overridepublic void onUIRefreshBegin(PtrFrameLayout var1) {this.init();}@Overridepublic void onUIRefreshComplete(PtrFrameLayout var1) {this.onUIRefreshComplete();}@Overridepublic void onUIRefreshPrepare(PtrFrameLayout var1) {this.onUIRefreshPrepare();}@Overridepublic void onUIReset(PtrFrameLayout var1) {this.onUIRefreshPrepare();}public void setShowRefreshInfo(boolean showRefreshInfo) {this.isShowRefreshInfo = showRefreshInfo;}
}
复制代码
下拉刷新调用方式
/*** 郑重声明:本源码均来自互联网,仅供个人欣赏、学习之用,* 版权归36氪产品发行公司所有,任何组织和个人不得公开传播或用于任何商业盈利用途,* 否则一切后果由该组织或个人承担。* 本人不承担任何法律及连带责任!请自觉于下载后24小时内删除**/
public class MainActivity extends AppCompatActivity implements PtrHandler {private PtrFrameLayout mPtr;private KrHeader krHeader;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mPtr = (PtrFrameLayout) findViewById(R.id.ptr);this.krHeader = new KrHeader(this);krHeader.setShowRefreshInfo(true);krHeader.getCompleteView().setText("暂无更新内容");this.mPtr.setHeaderView(this.krHeader);this.mPtr.addPtrUIHandler(this.krHeader);this.mPtr.setPtrHandler(this);this.mPtr.setDurationToCloseHeader(1000);this.mPtr.setDurationToClose(200);this.mPtr.setLoadingMinTime(1000);this.mPtr.setEnabledNextPtrAtOnce(true);ImageView iv_test = findViewById(R.id.iv_test);iv_test.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {startActivity(new Intent(MainActivity.this, TestActivity.class));}});}@Overridepublic boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {return true;}@Overridepublic void onRefreshBegin(PtrFrameLayout frame) {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {mPtr.refreshComplete();}}, 300);}
}
复制代码
2.2.5 源码github地址
希望能得到你的一个star,这是对我写这篇博文给大家带来一些另类思路和收获的鼓励 36KrRefreshDemo
3. 《作业盒子》批改控件提取
基本上与36KrRefresh类似,关键点都在于更多的耐心以及能提取成功的信心 这个控件提取遇到了更多的困难: ①控件涉及多个自定义view,再加上代码混淆的影响下,才确定这个控件涉及3个view,提取难度加大不少 ②控件在app中出现的层级更深,定位的时间耗费更多 ③控件中的绘制逻辑更加复杂,需要更多精力去理解混淆后的代码 PS:过程中发现了这个控件的一个bug:放大倍数过大,OOM,应用闪退.
源码地址: DrawContainerDemo 欢迎star,小小鼓励一下我~
4.授人以鱼不如授人以渔
总结一下反编译参考竞品的技巧:
- 先看主干,再细看旁枝末节. 什么意思呢?就是先看其大体项目架构,用了什么开源库,浏览一下AndroidManifest,都有什么Activity,通过英文单词去猜测其功能(一个优秀的项目,对类的命名必然是直观易懂的)
- 实践动手.这个也很重要,因为单纯看来的是不准确的,是不可能深入理解其核心逻辑的,必须尽可能的将其抽取出来做成demo,以此验证自己的猜测.当然,动手是一种冒风险的事情,因为有可能自己的猜测落空.
- 信心. 心里面的想法:我就有预感自己会成功,蜜汁自信.
- 耐心. 这事情没那么容易,但真没那么难. ####5. 反编译的实用价值(教你偷懒) 不懂偷懒的程序员,不是好程序员.
5.1功利性价值
核心业务的复杂功能实现,可能需要一个月,但是如果你通过反编译源码级别地了解竞品,借鉴竞品,说得粗俗点,竞品脱了裤子让你观摩,那你完成这个功能可能只需要1个星期,节省了三个星期,开发效率提高300%
5.2 自我价值
只是为了工作敲代码的程序员,就有点shameless了.不应该只看到其功利性价值,更应该去挖掘自我价值,学习一些优秀程序员敲的商用级别代码; 这两个反编译过程,我是带着强烈好奇心去完成的:
- 卧槽,怎么他们的下拉控件做的这么好?
- holy shit,怎么他们的批注实现思路这么赞呢? 非常珍惜这种好奇心.好奇心是个好东西,会驱动你去做更多以前没做过的事,让你有更多激动人心的发现,会更加想变得优秀,当然,也会让自己更加开心.
4.28日更新:反编译注意事项
经老上司彼时芒种提醒,新增了两个编译小技巧
- JD-GUI 可以查看更完备的源代码 比如下图中所示中无法显示的源代码.
- 寻找目标功能或者目标控件源码时,可通过adb命令查找应用当前界面所对应的activity名称加快寻找效率
adb shell dumpsys activity | grep "Running activities" -A 7
复制代码
最后,感谢大家看完我的文章,觉得不错的话,点个❤️
APK 控件逆向工程(36氪,作业盒子)相关推荐
- 【无私分享】修订版干货!!!一个炫酷的自定义日历控件,摆脱日历时间选择烦恼,纯福利~...
可能不少的小伙伴都有看楼主昨天发的自定义日历控件,虽然实现功能不多,但也还算将就吧. 没有看的小伙伴如果有兴趣的话可以去看看:http://www.cnblogs.com/liushilin/p/57 ...
- Android自定义日历控件,自带农历节假日,已经开源,即取即用~
关注本人的更多博客:http://www.cnblogs.com/liushilin/ 该自定义日历控件已经开源:github地址 可能不少的小伙伴都有看楼主昨天发的自定义日历控件,虽然实现功能不多, ...
- firefox扩展开发(四) : 更多的窗口控件
firefox扩展开发(四) : 更多的窗口控件 2008-06-11 17:00 标签盒子 标签盒子是啥?大家都见过,就是分页标签: 对应的代码: <?xml version="1. ...
- java自定义日历控件_【无私分享】修订版干货!!!一个炫酷的自定义日历控件,摆脱日历时间选择烦恼,纯福利~...
可能不少的小伙伴都有看楼主昨天发的自定义日历控件,虽然实现功能不多,但也还算将就吧. 但是看了的小伙伴就很心急了,说楼主上传到gitHub的东西有问题,楼主下载来看了看,基本都没问题吧,没弄好的小伙伴 ...
- 在 GUI 控件中使用布局和容器: CBOX 类
目录 1. 介绍 2. 目标 3. 类 CBox 3.1. 布局样式 3.2. 计算控件之间的间隔 3.3. 对齐 3.4. 部件渲染 3.5. 部件大小调整 3.6. 递归渲染 4. 在对话框窗口里 ...
- 背水一战 Windows 10 (36) - 控件(弹出类): ToolTip, Popup, PopupMenu
原文:背水一战 Windows 10 (36) - 控件(弹出类): ToolTip, Popup, PopupMenu [源码下载] 背水一战 Windows 10 (36) - 控件(弹出类): ...
- python tkinter控件_python GUI作业:使用tkinter的重要控件
题目1:使用tkinter的重要控件 绘制如下菜单: 图片.png 参考代码:#!/usr/bin/env python3# -*- coding: utf-8 -*-# 技术支持:https://w ...
- android 环境配置和安装, Android系统包说明,基本控件,常用代码,ADB 命令行,APK文件确解,小技艺,...
一. 环境配置和安装(Android2.2) 参考文章:这里 1.1 JDK 1.2 SDK 下载地址:http://dl.google.com/android ...
- 图片盒子控件 winform 114868210
图片盒子控件 winform 114868210 引入控件 放图片 控制图片的大小 效果 图片变的与控件一模一样大
最新文章
- Build Boost C++ libraries for x32/x64 VC++ compilers on Windows
- Linux中以单容器部署Nginx+ASP.NET Core
- SpringBoot集成Flowable_Jsite已发任务菜单报500
- 怎么把4399小游戏的代码_25行代码带你爬取4399小游戏数据,看下童年的游戏是否还在...
- hashmap修改对应key的值_死磕 java集合之HashMap源码分析
- 团队作业4——第一次项目冲刺(Alpha版本)
- cvs数据导入工具 oracle_oracle数据库的导入导出
- DP--POJ1191 棋盘分割
- Python网页抓取教程
- 北京市海淀区卫星地图离线包下载
- ncl 添加点shp文件_NCL绘制中国地图
- 地铁一公里造价达7亿元,大部分城市无法回本,为何还抢着建?
- 计算多项式的小技巧(Horner法则)
- 腾讯互娱2021年游戏客户端开发工程师暑期实习生面试经历
- 计算机二级的公共基础课水平测试,计算机二级公共基础知识复习试题含答案
- oracle实例由,Oracle 数据库的实例由( )组成
- 【重磅】百度智能运维工程架构
- JavaScript高级第02天笔记
- 4.28 DP练习赛
- Chemical Peel Treatment For Acne Scars
热门文章
- 计算机硬件系统设计—码表数码管显示驱动设计
- 画中画效果自由制作,视频、图片都可制作
- vue中使用echarts折现图表(多图表与定时递归刷新图表解决方案在附录1、2中)
- linux服务器安装gmt,linux GMT简易安装
- 解析几何--对称,平移和旋转
- LFS 好辛苦的工作,据说要坚持至少12个小时,而如果不用“风无痕9588”大侠的脚本代码而手工输入的话,据说要200个小时!
- 谷歌插件 XssSniper 扩展介绍 xss检测
- Netfilter源码分析
- 从腰椎间盘突出到坐骨神经痛,这个过程怎么度过与规避!
- 不同VLAN下实现网络互相通信(配置port trunk pvid vlan进行数据转发)