使用Anko创建快400%的布局

原文:https://medium.com/@vergauwen.simon/400-faster-layouts-with-anko-da17f32c45dd#.ehmantke1 

我用Anko已经有了一段时间了,很好奇Anko提供了哪些优势。所以做了一些性能测试。

4A99D00E-04BF-4FAA-BD76-E720C7F43265.png

我打算把一个比较高级的布局迁移到Anko。这个布局包含了一个RelativeLayout,里面有17个ImageView(全是用的.9图片),一个SurfaceView作为取景器,两个普通的TextView。仍然是一个比较干净的布局,因为没有嵌套。

为什么Anko性能更优?

XML布局是在运行时解析的。也就是说XML需要从资源文件中获取,然后XmlPullParser需要解析所有的元素并一个一个的创建它们。还要解析元素的属性,然后设置。这个过程非常繁重,那么到底浪费了多长时间在上面呢?

1-cZgL3CXQrkeAeW7WrzYcrg.png

我在4个老旧的设备上运行了测试,虽然老但又是每个安卓开发者都需要处理的设备。我们在所有的设备上运行了大约4次 DevMetrics。结果发现性能差距居然达到了350%-600%的程度。

随着对设计/动画以及性能的需求日益增加,我认为大家都对应用的速度非常关心,所以为什么要使用Anko就不用多说了吧。

除了布局变快了之外,我们现在构建布局是在运行时,所以可以加上一些逻辑判断。让我们用Anko创建一个master-detail的示例:

class MainActivity : AppCompatActivity() {
  private var toolBar: Toolbar? = null
  private var container: ViewGroup? = null
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    coordinatorLayout {
      fitsSystemWindows = true
      appBarLayout {
        toolBar = toolbar {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) elevation = 4f
        }.lparams(width = matchParent, height = actionBarSize())
      
      }.lparams(width = matchParent)
      container = frameLayout()
        .lparams(width = matchParent, height = matchParent) {
          behavior = AppBarLayout.ScrollingViewBehavior()
        }
    }
  }
}

MainActivity.kt hosted with ❤ by GitHub

就如你看到的那样,用Anko写布局跟xml没有什么两样。除了布局构建上的优势之外,我们可以做兼容性检查,根据OS的版本设置elevation,而不是在布局目录中写两个xml。

对于提取MainActivity中的的元素,Anko提供了AnkoComponent来解决这个问题,但是你还是需要使用findViewById和类型转换。不过这也是可以轻松解决的。

我们创建一个提供了一个bind 和一个unbind方法的接口。类似于使用Butter Knife来绑定与解绑。

interface ViewBinder<in T> {
    fun bind(t: T) : View
    fun unbind(t: T)
}

ViewBinder.kt hosted with ❤ by GitHub

现在我们可以轻松的从前面的布局中提取出元素到MainLayout.kt class。

class MainLayout : ViewBinder<MainActivity> {
  override fun bind(mainActivity: MainActivity): View =
    mainActivity.UI {
      coordinatorLayout {
        fitsSystemWindows = true
        appBarLayout {
          mainActivity.toolBar = toolbar {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) elevation = 4f
          }.lparams(width = matchParent, height = actionBarSize())
      
        }.lparams(width = matchParent)
        mainActivity.container = frameLayout()
          .lparams(width = matchParent, height = matchParent) {
            behavior = AppBarLayout.ScrollingViewBehavior()
          }
      }
    }.view
  override fun unbind(mainActivity: MainActivity) {
    mainActivity.container = null
    mainActivity.recycView = null
  }
}

MainLayout.kt hosted with ❤ by GitHub

public class MainActivity extends AppCompatActivity {
    LinearLayout container;
    RecyclerView recycView;
    FrameLayout detailContainer;
    
    private MainLayout mainLayout = new MainLayout();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(mainLayout.bind(this));
        ...
    }
}

现在我们的activity看起来是不是干净多了呢?除了调用了setContentView(mainLayout.bind(this))之外没有什么特别之处,它将设置content view并把view和变量绑定。就如你看到的那样,无需findViewById和类型转换。

Runtime layouts

如果你想在构建布局的时候加入逻辑,你肯定会喜欢这个东西。

configuration(orientation = Orientation.LANDSCAPE, smallestWidth = 700) {
  recyclerView {
    init()
  }.lparams(width = widthProcent(50), height = matchParent)
  
  frameLayout().lparams(width = matchParent, height = matchParent)
}
fun <T : View> T.widthProcent(procent: Int): Int =
  getAppUseableScreenSize().x.toFloat().times(procent.toFloat() / 100).toInt()

RuntimeLayouts.kt hosted with ❤ by GitHub

让我们分析一下上面的代码,anko提供了一个语法友好的configuration用于在运行时检查配置。可以检查screenSize, density, language, orientation, fromSdk, sdk, uiMode, nightMode, rightToLeft 以及 smallestWidth。因此这里的layout DSL只有在设备为横向、横向宽度是700的时候才会运行。

我们还可以轻松计算出大小,这里将在运行时计算出屏幕宽度并把内容宽度设置为屏幕宽度的50% 。

总结

Anko提供了几种解决传统xml布局构建方式的办法。它绕开了xml布局方式的所有开销。你无需处理findViewById或者类型转换,而且在运行时构建布局可以让你添加想要的逻辑,让布局更动态。所有这些只需要花很小的代价

这里所提到的代码都可以在github的项目中找到。