分类 Android 下的文章

前言

最近项目中有用到LiveData+ViewModel的架构组件,今天来学习一波。本篇文章参考:MVVM 架构,ViewModel和LiveData
所有语言为Kotlin。


LiveData

LiveData是一个可以被观察的数据持有者.这也就意味着应用中的组件能够观察LiveData对象的更改,而无需在它们之间创建明确的和严格的依赖关系。这将完全分离LiveData对象使用者和LiveData对象生产者。
除此之外,LiveData还有一个很大的好处,LiveData遵守应用程序组件(活动,片段,服务)的生命周期状态,并进组件的生命周期管理,确保LiveData对象的内存泄漏。
注:上述的遵守应用程序组件的生命周期是指,譬如说Activity的生命周期在onStart、onResume的状态下是可以接收到LiveData内的数据变化回调的。而在Stop之后数据变化不会进行回调。而且在Activity销毁的时候,LiveData也会随之销毁,简单理解为它和Activity的生命周期类似,这也防止了内存泄漏。

在开发中我们多用的是继承LiveData的子类MutableLiveData<T>。里面还有两个方法,setValue和postValue,setValue必须在主线程调用。postValue可以在后台线程中调用。


ViewModel

ViewModel是和Model(数据层)进行交互,并且ViewMode可以被View观察.ViewModel可以选择性地为视图提供钩子以将事件传递给模型.该层的一个重要实现策略是将Model与View分离,即ViewModel不应该意识到与谁交互的视图。

通俗地来说,ViewModel所承担的职责就是UI层与数据层的中间层。

  • 一方面可以减轻View层的逻辑,View层可以更好的针对UI做设置而无需考虑其他逻辑。
  • 另一方面ViewModel作为View和Model的中间层,将View和Model解耦合。

此外,ViewModel还有一个比较厉害的功能,通常情况下activity会被系统自动销毁,此时我们用于保存数据的方式就是事先在onSaveInstanceState()方法中规定好需要存储的数据。但是这种做法有一个缺点就是保存的数据要经过序列化。使用ViewModel的话ViewModel会自动保留之前的数据并给新的Activity或Fragment使用。直到当前Activity被系统销毁时,Framework会调用ViewModel的onCleared()方法,我们可以在onCleared()方法中做一些资源清理操作。


使用ViewModel+LiveData编写一个简易demo

1、首先我们需要编写一个实体类,用于数据的封装:

`data class AccountBean(

    val name:String = "",
    val number:String = "")`

2、创建一个自定义的ViewModel:

`class AccountModel : ViewModel() {
    val accountLiveData = MutableLiveData<AccountBean>()

    fun getData(){
        accountLiveData.value = AccountBean("cyber","2077")
    }

    override fun onCleared() {
        super.onCleared()
    }
}`

在这个ViewModel里面我们定义了一个MutableLiveData的对象其本质就是一个LiveData,而这个LiveData里的数据绑定的是刚刚我们定义的实体类型AccountBean。
getData方法是用于模拟数据更新的,当调用此方法时,我们会设置上述的LiveData里的数据内容。上述的accountLiveData.value实际上是调用了MutableLiveData里的setValue方法。

3、View层的编写
接下来就是View层了,其实就是Activity层

xml:

`<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:text="Hello World!"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="217dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/text" />

</android.support.constraint.ConstraintLayout>`

kotlin:

`class MainActivity : AppCompatActivity() {

    private lateinit var mAccountModel:AccountModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //初始化ViewModel
        mAccountModel= ViewModelProviders.of(this).get(AccountModel::class.java)
        //点击按钮后更新LiveData的数据实体
        button.setOnClickListener{
            mAccountModel.getData()
        }
        //设置LiveData的观察者相应更新TextView
        mAccountModel.accountLiveData.observe(this, Observer {
            text.setText("名字:${it!!.name},号码:${it!!.number}")
        })
    }
}`

界面很简单,就是一个TextView和一个Button。
首先我们需要将刚刚写好的ViewModel作为对象创建出来然后再onCreate时候初始化,这里需要注意的是Kotlin默认定义对象时需要初始化,所以这里使用lateinit关键字来声明对象需要延迟初始化。
点击按钮的时候,我们模拟数据更新调用刚刚在自定义ViewModel中的getData方法更新LiveData里的数据。
此处我们注册了LiveData的观察模式,当数据发生变化时会回调上述的observe方法,而在Observer对象中其实是有一个onChange方法的,上述的it代表的其实就是刚刚改变的那个数据(AccountBean对象)。

前言

最近在学习自定义控件的绘制,今天来学习一下Android Canvas——画布的相关api。


drawXXX系列

  • canvas.drawArc——画扇形

    drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint)
    drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

通过所规定的矩形(见第一个方法的RectF),以矩形的中心点为坐标原点绘制此扇形。

startAngle 开始旋转的位置 (0°在坐标轴的三点钟方向)
sweepAngle 需要旋转多少度 (大于360,则画出一圈)
useCenter 是否使用中心点,true时起点和终点都会连接矩形的中心点;false时起点和终点会直接连接起来。
如:

RectF rectF=new RectF(200,200,400,400);
canvas.drawArc(rectF,0,270,true,paint);          //画一个扇形 角度顺时针转动

从0°开始顺时针旋转270°的扇形

  • canvas.drawCircle——绘制圆形

    drawCircle(float cx, float cy, float radius, Paint paint)

cx、cy 圆的圆心坐标
radius 圆的半径
如:

canvas.drawCircle(400,400,200,paint);

**绘制一个圆心为(400,400),半径为200的圆形。
注:当画笔设置了 StrokeWidth 时,圆的半径=内圆的半径+StrokeWidth/2**

  • canvas.drawBitmap——绘制图像

    drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)

matrix 构建的矩阵作用于将要画出的位图
如:

Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
Matrix matrix=new Matrix();
matrix.postTranslate(100,100);
matrix.postRotate(45);
canvas.drawBitmap(bitmap,matrix,paint);
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)

src 规定绘制bitmap上src区域的部分图像
dst 规定绘制的区域大小(图像填满整个区域)
如:

Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
Rect src=new Rect(50,50,100,100);
Rect dst=new Rect(100,100,200,200);
canvas.drawBitmap(bitmap,src,dst,paint);

drawBitmap(Bitmap bitmap, float left, float top, Paint paint)

left 绘制的起点(左上角)距离左边的距离
top 绘制的起点(左上角)距离顶部的距离

drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint) 

用于网格扭曲,水波等的绘制。

  • canvas.drawColor,drawRGB,drawARGB——在整个绘制区域统一涂上指定的颜色

    canvas.drawColor(Color.GREEN);
    canvas.drawRGB(100,150,200);
    canvas.drawARGB(100,100,150,200);
    该方法一般用于给区域添加一个半透明的遮罩层。

  • canvas.drawPoint——绘制圆点

    drawPoint(float x, float y, Paint paint)
    x,y 点的坐标

注:点的大小可通过paint.setStrokeWidth(30)设置,点的形状可通过paint.setStrokeCap(Paint.Cap.ROUND)设置。

  • canvas.drawPoints——绘制一组圆点

    drawPoints(float[] pts, Paint paint)
    drawPoints(float[] pts, int offset, int count,Paint paint)
    float[] pts 绘制圆点的坐标数组(数组每相邻两位形成一个坐标)

offset 从数组的第几个位置开始计算坐标
count 需要绘制的圆点个数
如:

float[] points = {0, 0, 50, 50, 50, 100, 100, 50, 100, 100, 150, 50, 150, 100};
canvas.drawPoints(points,paint);
  • canvas.drawOval——绘制椭圆

    drawOval(float left, float top, float right, float bottom, Paint paint)
    drawOval(RectF rect, Paint paint)

4个参数分别是上下左右的边界点

  • canvas.drawLine——绘制线

    drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
    startX, startY, stopX, stopY 分别是线的起点和终点坐标。

  • canvas.drawLines——绘制一组线

    drawLines(float[] pts, int offset, int count, Paint paint)
    drawLines(float[] pts, Paint paint)
    与上述的绘制一组圆点同理~

  • canvas.drawRoundRect——绘制带圆角的矩形

    drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
    drawRoundRect(RectF rect, float rx, float ry, Paint paint)
    与绘制矩形同理~

前言

这篇文章总结一下一些代码优化的问题,本文出自代码优化


String字符串优化

最常见的例子就是当你要频繁操作一个字符串时,使用StringBuffer代替String。
还比如:使用int数组而不是Integer数组。
避免创建短命的临时对象,减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。


ListView优化

  • Item布局,层级越少越好,使用hierarchyview工具查看优化。
  • 复用convertView
  • 使用ViewHolder
  • item中有图片时,异步加载
  • 快速滑动时,不加载图片
  • item中有图片时,应对图片进行适当压缩
  • 实现数据的分页加载

减少不必要的全局变量

尽量避免static成员变量引用资源耗费过多的实例,比如Context。
因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。
你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。


Cursor(游标)回收

Cursor是Android查询数据后得到的一个管理数据集合的类,在使用结束以后。应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。


Receiver(接收器)回收

调用registerReceiver()后未调用unregisterReceiver().
当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册
也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现,通常我们可以重写Activity的onDestory()方法,在onDestory里进行unregisterReceiver操作


Stream/File(流/文件)回收

主要针对各种流,文件资源等等如:
InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap图片等操作等都应该记得显示关闭。


避免内部Getters/Setters

在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。
for循环
访问成员变量比访问本地变量慢得多,如下面一段代码:

for(int i =0; i < this.mCount; i++)  {}  

永远不要在for的第二个条件中调用任何方法,如下面一段代码:

for(int i =0; i < this.getCount(); i++) {}  

对上面两个例子最好改为:

int count = this.mCount; / int count = this.getCount();  
for(int i =0; i < count; i++)  {}