O_o
O_o
文章目录
  1. 前言
  2. Activity 中的KAE
  3. Fragment 中的KAE
  4. ViewHolder 中使用 KAE
  5. KAE 的反思
  6. ViewBinding

What's inside?-KAE

前言

前不久和小曾聊天得知KAE:kotlin-android-extension 被废弃了,取而代之的是 ViewBinding,淦,我好像之前完全不知道这件事情,也突然想起用KAE用得这么爽好像还从没真的去了解过它的原理呢。

Activity 中的KAE

通常像这种类型的框架归根结底都是帮助你去写了一些额外的代码,通常在编译期,比如以前常用的 butterKnife 利用 APT 在编译期生成一些辅助类。同样的思路来拆 KAE,Kotlin 文件最终都会编译成 jvm 的字节码,而这个字节码文件又可以转成Java的class文件,Android Studio 就可以帮我们快速的实现这个转换,具体怎么操作请自行 Google 吧。

首先我们写一个demo文件,在 layout 中定义一个 id 为 btnDemo 的 Button ,然后在activity中用KAE的方式来使用它

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnDemo.setOnClickListener {
// do nothing
}
}
}

接着看它编译成Java文件的样子, emm…简单明了,好像没什么可讲的了。

public final class MainActivity extends AppCompatActivity {
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(1300009);
// 通过 findCachedViewById 来获取button的实例
((Button)this._$_findCachedViewById(id.btnDemo)).setOnClickListener((OnClickListener)null.INSTANCE);
}
public View _$_findCachedViewById(int var1) {
// 创建缓存map,如果之前没有创建的话
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
// 尝试获取之前缓存的对象
View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
// 没取到缓存,通过 findViewById 来获取
var2 = this.findViewById(var1);
// id-实例 的方式存入缓存map中去
this._$_findViewCache.put(var1, var2);
}
// 返回view
return var2;
}
// 清除缓存
public void _$_clearFindViewByIdCache() {
if (this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
}

嗯?_$_clearFindViewByIdCache() 好像没有被调用呢?

Fragment 中的KAE

再来写一个简单的fragment的demo

class KaeFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 偷懒直接用activity的布局文件
return inflater.inflate(R.layout.activity_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnDemo.setOnClickListener { }
}
}

再看对应的Java文件,省略掉和activity相同的代码,可以看到它帮我们重写了 onDestroyView 并且在里面调用了 _$_clearFindViewByIdCache(),清除掉当前的 view 缓存

// $FF: synthetic method
public void onDestroyView() {
super.onDestroyView();
this._$_clearFindViewByIdCache();
}

为什么 Fragment 中需要调用而Activity 中不需要呢?不理解请自行Google生命周期。

ViewHolder 中使用 KAE

KAE 直接支持 Activity 和 Fragment ,而 ViewHolder 中要怎么使用呢?然而就在我今天看了 KAE 生成的代码后才发现,一直以来我的用法都是错误的,我相信有很多同学也跟我一样,在不了解原理的情况下很容易误用。

首先按照我们错误的方式来写(省略掉不重要的代码):

override fun onBindViewHolder(holder: DemoViewHolder, position: Int) {
holder.itemView.tvMovieName.text = "text"
}

这段代码看起来没什么问题,实际运行也没什么问题,看似也解决了传统写法中需要定义变量,然后findviewbyid的方式,但是事实真的是这样吗?

public void onBindViewHolder(@NotNull DemoAdapter.DemoViewHolder holder, int position) {
// ...省略掉了检查变量非空的代码
// 首先获取到 itemView
View var10000 = holder.itemView;
// 然后通过 itemView findViewById 获取到 TextView
TextView var3 = (TextView)var10000.findViewById(id.tvMovieName);
// 最后使用
var3.setText((CharSequence)"text");
}

看出问题了吗?没有? 那我们再改一下:

override fun onBindViewHolder(holder: DemoViewHolder, position: Int) {
holder.itemView.tvMovieName.text = "text1"
holder.itemView.tvMovieName.text = "text2"
}
public void onBindViewHolder(@NotNull DemoAdapter.DemoViewHolder holder, int position) {
View var10000 = holder.itemView;
TextView var3 = (TextView)var10000.findViewById(id.tvMovieName);
var3.setText((CharSequence)"text1");
var10000 = holder.itemView;
var3 = (TextView)var10000.findViewById(id.tvMovieName);
var3.setText((CharSequence)"text2");
}

看出问题了吗?淦,我们每次用 itemView.xxx 这种写法的时候,它都会通过itemView findViewById 来获取对象,根本都没有用到缓存。

现在我们来看正确的写法

  • 首先开启kae的实验功能
androidExtensions { experimental = true }
  • 让 ViewHolder 实现 LayoutContainer 接口
inner class DemoViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer
  • 最后直接通过 viewHolder.xxx 来调用
override fun onBindViewHolder(holder: DemoViewHolder, position: Int) {
holder.tvMovieName.text = "text"
}

再来看对应的Java 文件:

public void onBindViewHolder(@NotNull DemoAdapter.DemoViewHolder holder, int position) {
TextView var10000 = (TextView)holder._$_findCachedViewById(id.tvMovieName);
var10000.setText((CharSequence)"text");
}

熟悉的 _$_findCachedViewById 出现了。

ViewHolder 的分析暴露出来一个问题,当我们在使用一个框架的时候应该聚焦在它所解决的问题本身上面,还是应该聚焦在原理层面以便于更加正确的使用它?

对于这个问题,对于开发者自身的提高来说当然去了解原理是最好的。

KAE 的反思

那么另外一个问题,如果一个框架很容易被误用,那这个框架本身的设计是否也存在问题呢?

开篇的时候说了 KAE 已经被废弃了,废弃总要有个理由吧,优点我们都知道,原理也看了,要不然总结下缺点?

  • 本质上是通过findViewById来获取,由于id是公共访问的,可能会因为导入了不同的layout导致出错
    • emm…用传统的方式一样存在这个问题吧
  • 用了一个 HashMap 来做view的缓存,会无形中增加内存成本?
  • 通过HashMap 来访问,理论上没有直接用引用的方式快?

以上的理由在理论上当然都成立,但实际上有多少项目对于内存的压榨已经到了要考虑一个map缓存的程度呢?又有多少应用对于性能的要求高到计较map的读取速度呢?事实上,大部分程序员们写的狗屎代码所带来的内存和时间的消耗远远超过了KAE,另外有些文章还提到 KAE 只支持kotlin,可是现在Google主推的就是kotlin,很多库甚至都只有kotlin版本,所以我觉得也不成立。

KAE 有一个目前解决不了的问题就是无法跨模块,而Google现在似乎又在推广模块化,似乎这才是真正的原因?

ViewBinding

对于KAE的替代品,它又是怎么工作的,又是如何解决KAE没有解决的问题呢?我还没开始用。。。等我用一段时间再来分析 ViewBinding 的原理。

支持一下
如果对你有帮助,也可以支持下小站