Android之常见的使用技巧

文章目录

      • 1.全局获取Context的技巧
      • 2.使用Intent传递对象
        • Serializable方式
        • Parcelable方式
      • 3.定制自己的日志工具
      • 4.深色主题
      • 5.Java和Kotlin代码之间的转换

1.全局获取Context的技巧

在Android中,你会发现有很多地方都需要用到Context,例如:弹出Toast,启动Activity,发送广播,操作数据库,使用通知。
这个时候如果我们需要在项目的任何地方都能够获取到Context,Android提供了一个Application类,每当应用程序启动的时候,系统就会自动将这些类进行初始化。而我们可以定制一个自己的Application类,以便于管理程序内的一些全局的状态信息,比如全局Context
首先创建一个MyApplication

class MyApplication : Application() {
    companion object{
        lateinit var context: Context
    }

    override fun onCreate() {
        super.onCreate()
        context=this.applicationContext
    }
}

可以看到,MyApplication中的代码非常简单。这里我们在companion object中定义了一个context变量,然后重写父类的onCreate()方法,并将调用getApplicationContext()方法得到的返回值赋值给context变量,这样我们就可以以静态变量的形式获取context对象了。
接下来在AndroidManifest.xml文件中的< application >标签下进行指定就可以了。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.kotlintext">
 <application
        android:name="com.example.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.KotlinText">
        ...
    </application>
</manifest>

这样我们就实现了一种全局获取Context的机制。
接下来比如我们定义的showToast()方法就可以这样写

fun String.showToast(duration:Int=Toast.LENGTH_SHORT){
Toast.makeText(MyApplication.context,this,duration).show()
}

现在只需要使用如下用法就能弹出一段文字提示:

"This is Toast".showToast()

2.使用Intent传递对象

使用Intent来传递对象有两种方式:Serializable和Parcelable。

Serializable方式

Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法非常简单,只需要让一个类去实现Serializable这个接口就可以了。
比如有一个Person类,其中包含了name和age这两个字段,如果想要将它序列化,就可以这样写:

class Person :Serializable{
    var name=""
    var age=0
}

这里我们让Person类实现了Serializable接口,这样所有的Person对象都是可序列化的。

        var person=Person()
        person.name="Tom"
        person.age=20
        val intent=Intent(this,SecondActivity::class.java)
        intent.putExtra("person_data",person)
        startActivity(intent)

可以看到,这里我们创建了一个Person的实例,并将它直接传入Intent的putExtra()方法中。由于Person类实现了Serializable接口,所以才可以这样写。
接下来在SecondActivity中获取这个对象也很简单,写法如下:

 val person= intent.getSerializableExtra("person_data") as Person

这里调用了Intent的getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型为Person对象,这样我们就成功实现了使用Intent传递对象的功能。
需要注意的是,这种传递对象的工作原理是先将一个对象序列化成可存储或可传输的状态,传递给另外一个Activity后再将其反序列化成一个新的对象。虽然这两个对象中存储的数据完全一致,但他们实际上是不同的对象。

Parcelable方式

除了Serializable之外,使用Parcelable也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样就能实现传递对象的功能了
修改Person中的代码,如下所示

class Person() :Parcelable{
    var name=""
    var age=0

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeString(name)//写出name
        dest?.writeInt(age)//写出age
    }

    companion object CREATOR : Parcelable.Creator<Person> {
        override fun createFromParcel(parcel: Parcel): Person {
            val person=Person()
            person.name=parcel.readString() ?:""//读取name
            person.age=parcel.readInt()//读取age
            return person
        }

        override fun newArray(size: Int): Array<Person?> {
            return arrayOfNulls(size)
        }
    }

}

Parcelable的实现方式要稍微复杂一些。可以看到,首先我们让Person类实现了Parcelable接口,这样就必须重写describeContents()和writeToParcel()这两个方法。其中describeContents()方法返回0就可以了,而writeToParcel()方法,我们需要调用Parcel的writeXxx()方法,将Person类中的字段一一写出。
除此之外,我们还必须在Person类中提供一个名为CREATOR的匿名类实现。这里创建了 Parcelable.Creator接口的一个实现,并将泛型指定为了Person。接着需要重写createFromParcel()和newArray()这两个方法,在createFromParcel()方法中,我们要创建一个Person对象进行返回,并读取刚才写出name和age字段。其中name和age都是调用Parcel的readXxx()方法读取到的(注意这里读取的顺序一定要和刚才写出的顺序完全相同)而newArray()方法中的实现,只需要调用arrayOfNulls()方法,并使用参数中传入的size作为数组大小,创建一个空的Person数组即可。
接下来我们可以使用之前的代码传递Person对象,只不过在获取对象的时候需要稍加修改一下:

  val person = intent.getParcelableExtra<Person>("person_data")

但是这种方式实现起来比较复杂,为此Kotlin提供了另外一种更加简便的用法,但前提是要传递的所有数据都必须封装在对象的主构造函数中才行。
修改Person类中的代码,如下所示

@Parcelize
class Person(var name:String,var age:Int) :Parcelable{
}

对比一下,Serializable的方式较为简单,但是就将整个对象进行序列化,因此效率会比Parcelable方式低一些。

3.定制自己的日志工具

当我们编写一个比较庞大的项目,期间为了方便调试,在代码的多个地方打印了大量日志,当项目正式上线的时候仍然会照常打印,这样会降低程序的运行效率,还有可能将一些数据泄露出去。这个时候最理想的情况时能够自由地控制日志的打印,当程序处于开发阶段让日志打印出来,当程序上线之后就把日志屏蔽掉。
新建一个LogUtil单例类

object LogUtil {
    //const val 可见性为public final static
    private const val VERBOSE=1
    private const val DEBUG=2
    private const val INFO=3
    private const val WARN=4
    private const val ERROR=5
    private var level= VERBOSE
    fun v(tag:String,msg:String){
        if(level<= VERBOSE){
            Log.v(tag,msg)
        }
    }
    fun d(tag: String,msg: String){
        if(level<= DEBUG){
            Log.d(tag,msg)
        }
    }
    fun i(tag: String,msg: String){
        if(level<= INFO){
            Log.i(tag,msg)
        }
    }
    fun w(tag: String,msg: String){
        if(level<= WARN){
            Log.w(tag,msg)
        }
    }
    fun e(tag: String,msg: String){
        if(level<= ERROR){
            Log.e(tag,msg)
        }
    }

}

这样就把一个自定义的日志工具创建好了,比如打印一行DEBUG级别的日志就可以这样写:

 LogUtil.d("TAG","debug log")

打印一行WARN级别的日志就可以这样写:

LogUtil.w("TAG","warn log")

我们只需要通过修改level变量的值,就可以自由地控制日志的打印行为。比如让level等于VERBOSE就可以把所有的日志打印出来,让level等于ERROR就可以只打印程序的错误日志。

4.深色主题

深色主题除了让眼部在夜间使用时更加舒适之外,还可以减少电量消耗,从而延长手机续航。Android10.0及以上系统的手机,都可以在Settings→Display→Dark theme中对深色主题进行开启和关闭。开启深色主题后,系统的界面风格包括一些内置应用都会变成深色主题的色调。
Android之常见的使用技巧_第1张图片
但是你打开我们自己编写的应用程序,你会发现目前界面的风格还是使用的浅色主题模式,这就和系统的主题风格不同了,说明我们需要对此进行适配。
最简单的一种适配方式就是使用Force Dark,它是一种让应用程序快速适配深色主题,并且不用编写额外代码的方式。Force Dark的工作原理是系统会分析浅色主题应用下的每一层View,并且在这些View绘制到屏幕之前,自动将它们的颜色转换成更加适合深色主题的颜色。(注意,只有原本使用浅色主题的应用才能使用这种方式,如果你的应用原本使用的就是深色主题,Force Dark将不会起作用)。
启用Force Dark需要借助android:forceDarkAllowed属性

<item name="android:forceDarkAllowed">true</item>

这里android:forceDarkAllowed属性设置为true,说明现在我们是允许系统使用Force Dark将应用强制转换成深色主题的。
除此之外,还有另外一种实现方式,我们知道AppCompat库内置的主题恰好主要分为浅色主题和深色主题两类,比如Theme.AppCompat.Light.NoActionBar就是浅色主题,而Theme.AppCompat.NoActionBar就是深色主题。选用不同的主题,在控件的默认颜色等方面会有完全不同的效果。
而现在我们多了一个DayNight主题。使用了这个主题后,当用户在系统设置中打开了深色主题,应用程序会自动使用深色主题,反之使用浅色主题。
新建一个values-night

<resources xmlns:tools="http://schemas.android.com/tools">
    
    <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
        
        "colorPrimary">@color/purple_200
        "colorPrimaryVariant">@color/purple_700
        "colorOnPrimary">@color/black
        
        "colorSecondary">@color/teal_200
        "colorSecondaryVariant">@color/teal_200
        "colorOnSecondary">@color/black
        
        "android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant
        
    style>
resources>

可以看到,这里我们将AppTheme的parent主题指定成了Theme.AppCompat.DayNight.NoActionBar,这是一种DayNight主题。因此,在普通情况下应用程序仍然会使用浅色主题,但一旦用户在系统设置中开启了深色主题,应用程序会自动使用深色主题。
我们应该更多地使用能够根据当前主题自动切换颜色的主题属性。比如说黑色的文字通常应该衬托在白色背景下,反之白色的文字通常应该衬托在黑色的背景下。那么此时我们可以使用主题属性来指定背景以及文字的颜色,写法如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:attr/colorBackground"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World"
        android:layout_gravity="center"
        android:textSize="40sp"
        android:textColor="?android:attr/textColorPrimary"
        />
FrameLayout>

Android之常见的使用技巧_第2张图片
Android之常见的使用技巧_第3张图片
另外当你需要在浅色主题和深色主题下分别执行不同的代码逻辑,可以使用如下代码在任何时候判断当前系统是否是深色主题:

    fun isDarkTheme(context: Context):Boolean{
        val flag=context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
        return flag==Configuration.UI_MODE_NIGHT_YES
    }

调用isDarkTheme()方法,判断当前系统时浅色主题还是深色主题,然后根据返回值执行不同代码逻辑即可。

5.Java和Kotlin代码之间的转换

Android之常见的使用技巧_第4张图片
Android之常见的使用技巧_第5张图片
Android之常见的使用技巧_第6张图片
点击窗口左上角的Decompile按钮,就可以将之前Kotlin代码编译成Java代码。

你可能感兴趣的:(Android,android,kotlin,开发语言)