👋1. 云后备介绍和 Git
😀程序员嘛,当然要有后备啦。Git 是其中云后备的一种。
👶🏻为啥要加这个家伙呢,Ctrl+Z 不就搞定了吗?
🙀那怎么可能呢!Ctrl+Z是有限的,打着打着就回不去了。要吧死机了,停电,手提被偷了,后备进水了, 要多衰有多衰,足额衰神上身。你能重来吗?噢嘛呢吧咪哄?(🔈 🔉 🔊)
👶🏻不会吧,这是我吗?你是卖保险的吗?
👴🏻这不有云后备嘛!每个新手都要有,很好用,想怎么倒就怎么倒。Git 是其中一个老牌啦,上 Github 开个户口,不要钱,老子喜欢免费的。你会不会啊?不会的出门转左有个模仿塑像的。
👱🏻是啊,在黑苹果上, XCode 12 开头就要你加,我现在学 IOS,正一头雾水呢。还是拿个 Android Studio 示范示范吧。
👉2. 安卓开个新玩家 Git Practice
| 图像 | 活动 |
|---|---|
![]() | 打开 Android Studio, 开个空的活动 。 |
![]() | 就是 Git Practice 啦! 😄Package name 用自己的名字。 |
👀3. 加 Git
趁现在还没划地盘,把 Git 加上去。👶🏻:刚开始了,就加啦?👱🏻:对啊,省得忘了,人一干起来,就天昏地暗地。
下载 Git: Git 下载
![]() | 装 Git 之后,在 Android Studio 打开 设定 Settings。 |
|---|---|
![]() | 跳到下面 Version Control => Git |
![]() | 在 Update method, 方式选 Merge 。 |

按 Test,让自己找对象啦。
重启,回来用终端 Terminal,看看能用否?

🔑4. 加 GitHub
到 GitHub 开户口: Github
【🔑】拿钥匙
![]() | 在 GitHub 内, 右上角 |
|---|---|
![]() | 左边目录, 选Developer Settings |
![]() | 左边目录, Personal access tokens 私人钥匙 |
![]() | Generate new token 设定新钥匙 |
![]() | 干啥的?留个记录 |
![]() | 资料库权限 |
![]() | 管理员组织权限: 主要是读的那个 |
![]() | 分享权 |
![]() | 建立 |

【🔑】插钥匙
👶🏻:呕也,有钥匙啦!。。。咋用呢?
😇:回 Android Studio,打开 设定Settings
![]() | 这次下到 Version Control => Github。 |
|---|---|
![]() | 插钥匙。 |
![]() | 你要留意它的三大要点。 Add Account加户口。 |
![]() | 我名字有了。 你的呢? |
【🔑】上传文档
😷OK,完工。哦,还没有,要把这份项目上传。
![]() | Enable Version Control 开启版本控制 |
|---|---|
![]() | 谷歌只安了三个,其他的要自己加。 |
![]() | 上传 |
![]() | 管他呢,全加去。 |
![]() | 做个记号。Commit上传。 |
![]() | 👶:有问题啊! 🤓:正常啦,继续Commit上传。 |
【🔑】 .gitignore
第一次总算干完了,看看多了啥。

外边多了个 .gitignore 文件。
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
🤖:里面装的都是不用上传的文件,建议你把任何秘密文件都加进去,不然你知我知大家都知。你都不希望见到老板这个样子:🤬。
🍛5. MVVM 划地盘, 加 Gradle
现在,做移动都要先划地盘。苹果都是老牌MVC,而安卓已经改成MVVM。倚天屠龙,谁与挣疯…风?
👶:啥叫划地盘啦?
😎:就是把编码划分成几个文件夹啦。
我是这样划的:

先这样,来点简单的。
加 Gradle 的资料库文件:
改 Project: 加 Dagger-Hilt

buildscript {
ext.kotlin_version = "1.4.20"
repositories {
google()
jcenter()
mavenCentral()
maven { url "https://oss.jfrog.org/libs-snapshot" }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0-alpha15'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// Hilt
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
...
Module:

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdkVersion 30
...
buildFeatures {
viewBinding true
}
}
dependencies {
// Test
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Assertions
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.ext:truth:1.3.0'
androidTestImplementation 'com.google.truth:truth:1.0'
// STD
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
// Design
implementation 'com.google.android.material:material:1.2.1'
// Layout
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
// Hilt
def hilt_version = '2.28-alpha'
def hilt_lifecycle_version = '1.0.0-alpha02'
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:$hilt_lifecycle_version"
kapt "androidx.hilt:hilt-compiler:$hilt_lifecycle_version"
// Tests
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
// Lifecycle
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// Test helpers for LiveData
def arch_version = "2.1.0"
testImplementation "androidx.arch.core:core-testing:$arch_version"
// Activity and Fragment
def activity_version = "1.2.0-beta01"
def fragment_version = "1.3.0-beta01"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
// Testing Fragments in Isolation
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
}
全部换成 androidx 版的, 因此要改 gradle.properties,加入:
...
android.useAndroidX=true
android.enableJetifier=true
Sync 同步。Ctrl + K:

叫 Gradle 吧,传上去再说。
🌏6. 改个【中英双语 】版
🤔先加个中文版面吧,以后的项目都可以抄这个。
[ 🀄️ ] 中文资源
![]() | 新资源 |
|---|---|
![]() | 中文版 |
res/values/strings 文件夹就了一个 strings.xml:

[ 🀄️ ] activity_main.xml 编辑

练习只要简单的, 只有一个文条和浮动按键。Layout 要改成:
<androidx.constraintlayout.widget.ConstraintLayout
IDE 里面的 FloatingActionButton 不好用,用私货:CoordinatorLayout+TextView。
localeFab:CoordinatorLayout 浮动按键
changeTV:TextView 按键文字
这样构图方便改,你可以把组合加捷径到 Live Template 的 XML 组。
所有 android:text="…" 选项都用 Alt+Enter 储存在 strings.xml 里面,当你换语言的时候可以一次更换。
[ 🀄️ ] 翻译器,加中文。
选探索(😅横幅最后那个,又搜上了,和 VS Code 差不多。)

输入: edit

加入中文文本啦:

📥7. Hilt 注射,比它老爸 Dagger🔪 简单
加个 Application(头,应用,随你叫):在 application 文件夹。
@HiltAndroidApp
class GitPracticeApp : Application()
当然,新头要加进 AndroidManifest.xml:
<application
android:name=".application.GitPracticeApp"
android:configChanges="locale"
。。。
然后 Activity 和 Fragment 的 class 前面要加
@AndroidEntryPoint
ViewModel 可以用 @ViewModelInject 直接注射,例如:
class BaseViewModel @ViewModelInject constructor(
private val storage: Storage
) : ViewModel() {
其它地方可以用 @Singleton 加 @Inject 注射,例如:
@Singleton
class FoodRepository @Inject constructor(
private val storage: Storage
) {
【 Hilt 插入Storage 】
Storage是内部储存(SharedPreference),我用来储存使用过的语言。分成两拨,一个是资源,一个注射器。

资源:
【 Storage 】界面
Storage.kt
interface Storage {
fun setString(key: String, value: String)
fun getString(key: String): String
fun setBoolean(key: String, value: Boolean)
fun getBoolean(key: String): Boolean
fun setInt(key: String, value: Int)
fun getInt(key: String): Int
fun delKey(key: String): Boolean
fun clear()
}
【 SharedPreferencesStorage 】样板戏
SharedPreferencesStorage.kt:老样子。
class SharedPreferencesStorage @Inject constructor(
@ApplicationContext context: Context
) : Storage {
private val fileName = "gitPractice"
private val sharedPreferences = context
.getSharedPreferences(fileName, Context.MODE_PRIVATE)
// String Value
override fun setString(key: String, value: String) {
with(sharedPreferences.edit()) {
putString(key, value)
apply()
}
}
override fun getString(key: String): String {
return sharedPreferences.getString(key, UNKNOWN)!!
}
// Boolean Value
override fun setBoolean(key: String, value: Boolean) {
with(sharedPreferences.edit()) {
putBoolean(key, value)
apply()
}
}
override fun getBoolean(key: String): Boolean {
return sharedPreferences.getBoolean(key, false)
}
// Integer Value
override fun setInt(key: String, value: Int) {
with(sharedPreferences.edit()) {
putInt(key, value)
apply()
}
}
override fun getInt(key: String): Int {
return sharedPreferences.getInt(key, 0)
}
// Remove value
override fun delKey(key: String): Boolean {
with(sharedPreferences.edit()) {
remove(key)
apply()
}
return !sharedPreferences.contains(key)
}
// Clear all data
override fun clear() {
with(sharedPreferences.edit()) {
clear()
apply()
}
}
}
然后,就可以注射了:
【 StorageModule 】注射药
StorageModule.kt:Hilt 继承了 Dagger 的传统,@Binds 跟 abstract,其它用 @Provides。不过已经简化了很多,都不用写 Component 了。
@InstallIn(ApplicationComponent::class)
@Module
abstract class StorageModule {
@Binds
abstract fun provideStorage(
storage: SharedPreferencesStorage
): Storage
}
🏹8. 注射 Storage 进 ViewModel
MVVM 嘛,Activity 当然有 ViewModel 做伙伴啦。
{ BaseActivity }
我这用 BaseActivity.kt:
Configuration 🤠 向 Context 🤯 鞠躬:“大哥,这是我的小弟, (Locale 😔)。旗人,改头换面的事,交代他干就行。”
Context 🤯: 不错,我要中文。
Locale 😔:我变。
Context 🤯:我要英文。
Locale 😔:I’m here。
Context 🤯:我要日文。
Locale 😲:大大哥,没有啊!变不了,没马甲啊。你瞧:
@Suppress("DEPRECATION")
@AndroidEntryPoint
abstract class BaseActivity : AppCompatActivity() {
private var mCurrentLocale: Locale? = null
private val baseVM: BaseViewModel by viewModels()
override fun onStart() {
super.onStart()
// Set default locale
val mLocale = baseVM.setDefault()
changeLocale(this, mLocale)
mCurrentLocale = resources.configuration.locale
lgd("BaseAct: Locale: $mCurrentLocale")
}
override fun onRestart() {
super.onRestart()
val locale = baseVM.getDefaultLocale()
if (locale != mCurrentLocale) {
mCurrentLocale = locale
recreate()
}
}
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
if (overrideConfiguration != null) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(baseContext.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
super.applyOverrideConfiguration(overrideConfiguration)
}
fun changeLocale(context: Context, locale: Locale?) {
lgd("BaseAct: changeLocale() ================> $locale")
val res: Resources = context.resources
val conf: Configuration = res.configuration
conf.setLocale(locale)
baseContext.resources.updateConfiguration(conf, baseContext.resources.displayMetrics)
}
}
简单,只是换语言 locale。不懂的地方看 【第 9 章】。
{ BaseViewModel }
BaseActivity 的 ViewModel – BaseViewModel.kt:
class BaseViewModel @ViewModelInject constructor(
private val storage: Storage
) : ViewModel() {
private var mLocale: Locale? = null
fun setDefault(): Locale {
lgd("BaseVM: setDefault()")
mLocale = getLocale(storage)
return mLocale as Locale
}
fun getDefaultLocale(): Locale? {
lgd("BaseVM: getDefaultLocale()")
return mLocale
}
}
🉑9. 加个通用文件夹 util,大家合用。

《 LocaleHelper 》
getLocale() 来自 util/LocaleHelper.kt:
里面只放一个量:用过什么语言(中文为第一选择)。
val Lang_Chinese: Locale = Locale.CHINA
val Lang_US_English: Locale = Locale.US
fun getLocale(storage: Storage): Locale {
lgd("LocaleHelper: getLocale()")
val language = storage.getString(LANGUAGE)
var locale: Locale? = null
when (language) {
UNKNOWN -> {
lgd("LocaleHelper: 无记录,中文为第一选择。")
storage.setString(LANGUAGE, CHINA)
locale = Lang_Chinese
}
CHINA -> locale = Lang_Chinese
US -> locale = Lang_US_English
}
return locale!!
}
《 Config 》
util 文件夹还有两个文件:
Config.kt:装通用的 String 文本代码
// Storage
const val UNKNOWN = "UNKNOWN"
const val CHINA = "China"
const val US = "United State"
const val LANGUAGE = "Language"
《 LogHelper 》
LogHelper.kt:看Logger,查错用的。
const val TAG = "MLOG"
fun lgd(s:String) = Log.d(TAG, s)
fun lgi(s:String) = Log.i(TAG, s)
fun lge(s:String) = Log.e(TAG, s)
你要在下面 Logcat 加过滤


这样你就可以直接看 标有 MLOG 的记录。
📱10. MainActivity 和 ViewModel
( MainActivity )
MainActivity.kt :这个不用加 @AndroidEntryPoint ,因为 BaseActivity 有。如果爸爸的爸爸有那么爸爸也不用加,有点绕口啊。
class MainActivity : BaseActivity() {
// ViewModel
private val mainVM: MainViewModel by viewModels()
val localeFab: CoordinatorLayout by lazy { findViewById(R.id.localeFab) }
val titleTV: TextView by lazy { findViewById(R.id.titleTV) }
val changeTV: TextView by lazy { findViewById(R.id.changeTV) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
localeFab.setOnClickListener {
lgd("MainAct: Clicked")
mainVM.swapLocale()
}
mainVM.currentLocale.observe(
this,
{ mLocale ->
lgd("MainAct: Observable Language => $mLocale ")
when (mLocale) {
CHINA -> {
lgd("MainAct: Selected China...")
changeLocale(this, Locale.CHINA)
}
US -> {
lgd("MainAct: Selected English(US)...")
changeLocale( this, Locale.US)
}
}
}
)
}
}
“by lazy” 挺好用,下面不用再写一堆东西。LiveData 在 ViewModel 里面,你要连着看。被监视的对象是 currentLocale 。
( MainViewModel )
MainViewModel.kt :
class MainViewModel @ViewModelInject constructor(
private val storage: Storage
) : ViewModel() {
val currentLocale = MutableLiveData<String>()
init {
// storage.clear() // 测试启动无资料时用的,可以删掉。
when (getLocale(storage)) {
Locale.CHINA-> currentLocale.value = CHINA
Locale.US-> currentLocale.value = US
}
}
fun swapLocale() {
val language = storage.getString(LANGUAGE)
lgd("MainVM: Current language = $language")
when (language) {
CHINA -> {
storage.setString(LANGUAGE, US)
currentLocale.postValue(US)
}
US -> {
storage.setString(LANGUAGE, CHINA)
currentLocale.postValue(CHINA)
}
}
}
}
看,写着写着就忘了上传 GitHub,这是坏习惯,赶紧 Ctrl+K :

⛑11. Logcat:找错 、加速。
Run 跑跑看,切,没用没有中文。看看下面 Logcat:
D: LocaleHelper: getLocale()
D: LocaleHelper: 无记录,中文为第一选择。
D: BaseVM: setDefault()
D: LocaleHelper: getLocale()
D: BaseAct: changeLocale() ================> zh_CN 《== 一次
D: BaseAct: Locale: zh_CN
D: MainAct: Observable Language => China
D: MainAct: Selected China...
D: BaseAct: changeLocale() ================> zh_CN 《== 两次
D: MainAct: Clicked
D: MainVM: Current language = China
D: MainAct: Observable Language => United State
D: MainAct: Selected English(US)...
D: BaseAct: changeLocale() ================> en_US
D: MainAct: Clicked
D: MainVM: Current language = United State
D: MainAct: Observable Language => China
D: MainAct: Selected China...
D: BaseAct: changeLocale() ================> zh_CN
开头 第 5 行 和 第 10 行 重复 了,要组织一下他们次序。Androidx 有点不一样,不会主动更新,看来要手动刷版。
先看 BaseActivity:
override fun onStart() {
super.onStart()
// Set default locale
val mLocale = baseVM.setDefault() // 这里输送方向错了
changeLocale(this, mLocale) // 重复
mCurrentLocale = resources.configuration.locale
lgd("BaseAct: Locale: $mCurrentLocale")
}
- MVVM 输送的方向是 单向 的,删除返回项目两个。
- 下面那个没有必要,可以直接在 ViewModel 内启动。
结果:
override fun onStart() {
super.onStart()
mCurrentLocale = resources.configuration.locale
lgd("BaseAct: Locale: $mCurrentLocale")
}
override fun onRestart() {
super.onRestart()
val locale = baseVM.mLocale
if (locale != mCurrentLocale) {
mCurrentLocale = locale
recreate()
}
}
BaseViewModel:setDefault 取消返回;增加开启项 init() 。
var mLocale: Locale? = null
init {
setDefault()
}
fun setDefault() {
lgd("BaseVM: setDefault()")
mLocale = getLocale(storage)
}
MainActivity:增加手动刷新。
override fun onCreate(savedInstanceState: Bundle?) {
。。。
mainVM.currentLocale.observe(
this,
{ mLocale ->
lgd("MainAct: Observable Language => $mLocale ")
when (mLocale) {
CHINA -> {
lgd("MainAct: Selected China...")
changeLocale(this, Locale.CHINA)
updateText() // 《 《 《 《 《 《 《 《 《 增加刷新
}
US -> {
lgd("MainAct: Selected English(US)...")
changeLocale(this, Locale.US)
updateText() // 《 《 《 《 《 《 《 《 《 增加刷新
}
}
}
)
}
private fun updateText() { // 《 《 《 《 《 《 《 《 《 手动刷新
lgd("MainAct: 更新版面, updateText().")
val changeText = getString(R.string.change_language)
changeTV.text = changeText
val helloText = getString(R.string.helloworld)
titleTV.text = helloText
val actionText = getString(R.string.app_name)
supportActionBar?.title = actionText
lgd("MainAct: Language swap: $changeText, $helloText, $actionText ")
}
Ctrl+K,上传 GitHub。

Run,再跑。

看看 Logcat :
D: LocaleHelper: getLocale()
D: LocaleHelper: 无记录,中文为第一选择。
D: BaseAct: Locale: en_US
D: MainAct: Observable Language => China
D: MainAct: Selected China...
D: BaseAct: changeLocale() ================> zh_CN
D: MainAct: 更新版面, updateText().
D: MainAct: Language swap: => 英语, 向大家问好!, Git 后备管理
D: MainAct: Clicked
D: MainVM: Current language = China
D: MainAct: Observable Language => United State
D: MainAct: Selected English(US)...
D: BaseAct: changeLocale() ================> en_US
D: MainAct: 更新版面, updateText().
D: MainAct: Language swap: => Chinese, Hello World!, Git Practice
D: MainAct: Clicked
D: MainVM: Current language = United State
D: MainAct: Observable Language => China
D: MainAct: Selected China...
D: BaseAct: changeLocale() ================> zh_CN
D: MainAct: 更新版面, updateText().
D: MainAct: Language swap: => 英语, 向大家问好!, Git 后备管理
没有重复,加速成功。
☕12. Espresso 自动测错
👶: 完了,下班啦!
👴: 喂,还没完呢,Espresso 呢?
👶: 下班还喝咖啡呀?
👴: 测错呢。
《 Espresso 记录 》


👴: 你啊,点一个加一个。我先走了。
👶: OK,一个一个又一个。不多,才六个。OK。

👽:跑起来试试,没结果,中风了?头不在啊,上网查啊,没有没有…
直接开谷歌的文件,有了。
《 Androidx Gradle Test 修改 》
Androidx 的 Test 有点不一样,加上 Kotlin , Gradle 的资料库要加 ktx。所以要改 module.gradle:
android {
compileSdkVersion 30
defaultConfig {
...
//testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//要用 androidx。上面那个根本不会跑。谷歌秀逗了,下班了还要自己弄。
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {...}
compileOptions { ...}
kotlinOptions {...}
buildFeatures {...}
// 我的能跑。
testOptions {
animationsDisabled = true // Espresso 要求的
unitTests {
includeAndroidResources = true
}
}
lintOptions {
abortOnError false
}
useLibrary 'android.test.runner'
useLibrary 'android.test.base'
useLibrary 'android.test.mock'
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/AL2.0'
exclude 'META-INF/LGPL2.1'
exclude("META-INF/*.kotlin_module")
}
}
dependencies {
// test
testImplementation 'junit:junit:4.13'
// arch
def archcore_version = '2.1.0'
androidTestImplementation "androidx.arch.core:core-testing:$archcore_version"
// AndroidJUnitRunner and JUnit Rules
def test_version = '1.3.0'
androidTestImplementation "androidx.test:runner:$test_version"
androidTestImplementation "androidx.test:rules:$test_version"
androidTestImplementation "androidx.test:core:$test_version"
androidTestImplementation "androidx.test:core-ktx:$test_version" // Kotlin 加的
// Assertions
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.2' // Kotlin 加的
androidTestImplementation 'androidx.test.ext:truth:1.3.0'
androidTestImplementation 'com.google.truth:truth:1.0'
// Espresso dependencies
def espresso_version = "3.3.0"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-accessibility:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-web:$espresso_version"
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-idling-resource:$espresso_version"
...
}
喔,曹,一大堆咧。
《 修改 Espresso 记录 》
开头:
@RunWith(AndroidJUnit4::class)
@LargeTest
class MainActivityTest {
@get:Rule
var mActivityTestRule: ActivityScenarioRule<MainActivity>
= ActivityScenarioRule(MainActivity::class.java)
把记录的码分类:
测中文的:
fun testChineseText() {
val textView = onView(
allOf(withId(R.id.titleTV), withText("向大家问好!"),
withParent(withParent(withId(android.R.id.content))),
isDisplayed()))
textView.check(matches(withText("向大家问好!")))
val textView2 = onView(
allOf(withId(R.id.changeTV), withText("=> 英语"),
withParent(allOf(withId(R.id.localeFab),
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup::class.java)))),
isDisplayed()))
textView2.check(matches(withText("=> 英语")))
val textView3 = onView(
allOf(
withText("Git 后备管理"),
withParent(
allOf(
withId(R.id.action_bar),
withParent(withId(R.id.action_bar_container))
)
),
isDisplayed()
)
)
textView3.check(matches(withText("Git 后备管理")))
}
测英文的:
fun testEnglishText() {
val textView4 = onView(
allOf(withId(R.id.titleTV), withText("Hello World!"),
withParent(withParent(withId(android.R.id.content))),
isDisplayed()))
textView4.check(matches(withText("Hello World!")))
val textView5 = onView(
allOf(withId(R.id.changeTV), withText("=> Chinese"),
withParent(allOf(withId(R.id.localeFab),
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup::class.java)))),
isDisplayed()))
textView5.check(matches(withText("=> Chinese")))
val textView6 = onView(
allOf(
withText("Git Practice"),
withParent(
allOf(
withId(R.id.action_bar),
withParent(withId(R.id.action_bar_container))
)
),
isDisplayed()
)
)
textView6.check(matches(withText("Git Practice")))
}
浮动按钮:
fun fabClick() {
val coordinatorLayout = onView(
allOf(withId(R.id.localeFab),
childAtPosition(
childAtPosition(
withId(android.R.id.content),
0),
1),
isDisplayed()))
coordinatorLayout.perform(click())
}
主控部分浓缩成几行,我跑它十次。
@Test
fun mainActivityTest() {
val scenarioRule = mActivityTestRule.scenario
for (i in 1..10) {
testChineseText()
fabClick()
testEnglishText()
fabClick()
}
scenarioRule.close()
}

超速啊!
Ctrl+K 上传:

《 测试 Resume 回来的结果 》
有时候,用家会离开App,又回去用这个App,所以要测 resume 有没有变味,在 scenario 内用 recreate()。
@Test
fun mainActivityTest2() {
val scenarioRule = mActivityTestRule.scenario
testChineseText()
fabClick()
testEnglishText()
fabClick()
scenarioRule.recreate()
testEnglishText()
scenarioRule.close()
}

失败,应该是英文的才对。我在 MainActivity 加个 @TestOnly 测试 专用的方程:
@TestOnly
fun getLocale(): String? {
return mainVM.currentLocale.value
}
接着就可以提取 locale 测试。用 for…loop,省事,中英连着测。
@Test
fun mainActivityTest2() {
val scenarioRule = mActivityTestRule.scenario
for (i in 1..2) {
var mLocale = ""
scenarioRule.onActivity { activity ->
mLocale = activity.getLocale()!!
Thread.sleep(2000)
}
lgd("Test2: locale = $mLocale")
testLocale(mLocale!!)
scenarioRule.recreate()
lgd("Test2: Resume locale = $mLocale")
testLocale(mLocale!!)
fabClick()
}
scenarioRule.close()
}
fun testLocale(mLocale: String) {
when (mLocale) {
CHINA -> testChineseText()
US -> testEnglishText()
}
}
跑跑看,太快了,加两秒减速。

😵:终于测完了,还有吗?
Ctrl+K,上传,收工。
🎁 13. 英文连接
这个是 英文简化版
帮帮忙啦,在那里拍拍手👏。
本文通过一个Android应用实例,介绍了如何使用Git进行版本控制,实现MVVM架构,结合Hilt进行依赖注入,以及使用SharedPreference存储数据。同时,文章详细讲解了Espresso测试和国际化切换的过程,帮助开发者提升开发效率和应用质量。



























被折叠的 条评论
为什么被折叠?



