本文阅读须要10分钟. 你可能的收获:html
每个android开发程序员颇有必要掌握一些逆向工程的知识技巧,其中好处不胜枚举,我细数一二:android
各位老铁能够下载体验一下这个app的***下拉刷新***,绝对让你颇有收获git
那么问题来了,假如你的项目中要实现这样一个控件,你须要多长时间? 1天? 2天? 个人答案是1个小时 反编译提取控件,理解其中绘制逻辑.找UI改改样式,over.程序员
嘻嘻,下面我会给个关于反编译技巧的教程,市面上大多数的应用脱了裤子给咱们看,刺激吧?github
还原实现下拉刷新控件shell
①控件位置难找; ②资源文件分散; ③代码通过混淆,代码逻辑须要跟着做者实现思路走一遍bash
1.ShakaApkTool 2.Apktool 3.Dex2Jar 4.Zipalign 5.SignApk 6.JDGUI架构
我是直接github上找到一个mac工具软件:android crack tool app
c.关键:在fragment中找到控件(须要一点点小耐心) 一开始没找着,想了一下,这个fragment确定是使用了下拉刷新的,位置没找错. 那问题出如今哪里呢?框架
in.srain.cube.views.ptr.PtrFrameLayout //第一反应是网上的开源库,github上一搜索,果真~
复制代码
36Kr使用比较出名的下拉刷新库github地址:android-Ultra-Pull-To-Refresh
d.根据下拉刷新头部KrHeader以及资源R文件定位资源文件
e. 根据KrHeader的变量LottieAnimationView b找到lottie动画 根据lottie文档官网,动画文件通常放在asserts文件或res/raw中
至此,这个控件已经被完彻底全的抽取出来了
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
*/
@Override
public 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);
}
}
@Override
public void onUIRefreshBegin(PtrFrameLayout var1) {
this.init();
}
@Override
public void onUIRefreshComplete(PtrFrameLayout var1) {
this.onUIRefreshComplete();
}
@Override
public void onUIRefreshPrepare(PtrFrameLayout var1) {
this.onUIRefreshPrepare();
}
@Override
public 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;
@Override
protected 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() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, TestActivity.class));
}
});
}
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
return true;
}
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mPtr.refreshComplete();
}
}, 300);
}
}
复制代码
但愿能获得你的一个star,这是对我写这篇博文给你们带来一些另类思路和收获的鼓励 36KrRefreshDemo
基本上与36KrRefresh相似,关键点都在于更多的耐心以及能提取成功的信心 这个控件提取遇到了更多的困难: ①控件涉及多个自定义view,再加上代码混淆的影响下,才肯定这个控件涉及3个view,提取难度加大很多 ②控件在app中出现的层级更深,定位的时间耗费更多 ③控件中的绘制逻辑更加复杂,须要更多精力去理解混淆后的代码 PS:过程当中发现了这个控件的一个bug:放大倍数过大,OOM,应用闪退.
源码地址: DrawContainerDemo 欢迎star,小小鼓励一下我~
总结一下反编译参考竞品的技巧:
核心业务的复杂功能实现,可能须要一个月,可是若是你经过反编译源码级别地了解竞品,借鉴竞品,说得粗俗点,竞品脱了裤子让你观摩,那你完成这个功能可能只须要1个星期,节省了三个星期,开发效率提升300%
只是为了工做敲代码的程序员,就有点shameless了.不该该只看到其功利性价值,更应该去挖掘自我价值,学习一些优秀程序员敲的商用级别代码; 这两个反编译过程,我是带着强烈好奇心去完成的:
经老上司彼时芒种提醒,新增了两个编译小技巧
adb shell dumpsys activity | grep "Running activities" -A 7
复制代码