[Kotlin] 玩Android代码学习之-模块化+Retrofit+协程+viewModel的数据层封装

news/2023/11/28 17:52:43

文章目录

    • 1:前言
      • 玩Android APP 源码
      • 本贴的目的
      • 参考贴
    • 2: kotlin下的模块化(捎带一嘴)
    • 3:Retrofit+协程+viewModel
      • 3.1基础网络层搭建
        • `lib_home:Banner`
        • `lib_common: BaseResp `
        • `lib_common:RetrofitManager`
        • `lib_home: HomeApi `
      • 3.2基础网络层接口测试
      • 3.3 基础网络层优化-koin依赖注入框架
        • 简单说一下`依赖注入`
        • koin的简单理解
        • koin的简单使用演示
      • 3.4 回到APP源码,解析 真正的homeModule
      • 3.5 结合 ViewModel 与协程,再看数据层处理
        • `HomeViewModel`的简单实现
        • `HomeRepo`的加入
        • `HomeViewModel`的最终调用
        • `HomeFragment`的调用及测试

1:前言

玩Android APP 源码

学习kotlin,学习Android,最好的方式就是看别人的APP源码, 手头有一份玩安卓APP的kotlin版本,适合学习使用,文中提到的代码都在这款APP的源码里

WanAndroid基础款(MVVM+Kotlin+Jetpack+组件化)

本贴的目的

主要目的是基于WanAndroid基础款(MVVM+Kotlin+Jetpack+组件化)这份源码, 学习kotlin开发, 顺便记录学习过程中一些相关知识.

1.了解在模块化下,使用Retrofit + 协程 + viewModel, 怎么用完成网络层 , 数据的处理
2. 复盘学习过程,记录思路的变化,加深对语言的理解
3.记录以供后期翻阅(或触类旁通,毕竟好记性不如烂笔头)

参考贴

  • Kotlin retrofit 协程 最简单的使用(一)
  • 扒一扒Koin
  • Koin简单使用

2: kotlin下的模块化(捎带一嘴)

基本和Java语言下的模块化配置差不多,主要是配置gradle,可以看这个
[Android 模块化配置实践] Java + Gradle7配置模块化实践记录

本项目的模块化截图:
在这里插入图片描述

从项目结构上看, lib_common模块里面,回提供基础的网络层封装, 在lib_home等业务模块中,依赖 lib_common提供的各项功能

//在lib_home中依赖 lib_common
dependencies {// 引入模块 lib_commonimplementation project(path: ':lib_common')}

3:Retrofit+协程+viewModel

乍一看APP源码, 确实有点晕的,所以我们拆分一下,先看我们熟悉的Retrofit部分

其实看完了Retrofit部分,我才发现,Retrofit是结合了协程一起的,所以第一步先尝试使用Retrofit+协程 ,完成简单的网络调用,再去结合viewModel会更加容易,也更符合开发顺序

至于为什么要结合协程?

// 不使用协程,返回值是Call类型,或者比如Java结合RxJava返回FLow
interface IApiServices {@GET("getHealthCare")fun getAllHealthData(@Query("userId") userId: String):Call<AllHealthBean>
}// 使用协程,返回值直接就是你定义的数据类型或者Bean,直接就是可以使用
interface IApiServices {@GET("getHealthCare")suspend  fun getAllHealthData(@Query("userId") userId: String):AllHealthBean
}

总之,Retrofit结合协程, 获取请求结果,使用起来更加方便, 这就有点类似 JS的 async/await

3.1基础网络层搭建

(http请求及数据展示)

先看看APP的接口
https://www.wanandroid.com/banner/json

{"data":[{"desc":"我们支持订阅啦~","id":30,"imagePath":"https://www.wanandroid.com/blogimgs/42da12d8-de56-4439-b40c-eab66c227a4b.png","isVisible":1,"order":2,"title":"我们支持订阅啦~","type":0,"url":"https://www.wanandroid.com/blog/show/3352"},{"desc":"","id":6,"imagePath":"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png","isVisible":1,"order":1,"title":"我们新增了一个常用导航Tab~","type":1,"url":"https://www.wanandroid.com/navi"},{"desc":"一起来做个App吧","id":10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":1,"title":"一起来做个App吧","type":1,"url":"https://www.wanandroid.com/blog/show/2"}],"errorCode":0,"errorMsg":""}

lib_home:Banner

(以上获取banner数据的bean类)

package com.example.lib_home.bean/*** @author: tiannan* @time: 2023/6/2.* @email: tianNanYiHao@163.com* @descripetion: 此处添加描述*/
data class Banner(val desc: String,val id: Int,val imagePath: String,val isVisible: Int,val order: Int,val title: String,val type: Int,val url: String
)

lib_common: BaseResp

( common模块下的Http请求的基本返回类)

http请求 也就是 code, msg,data, 前两个没什么好说的, 都是基本型数据, 唯独data可能是数组,可能是集合,类型不固定
只能在具体的请求数据里,定义出了相对应的Bean,才能说给出一个类型,所以这里的data,理所当然的用到泛型
APP源码中用了 T 作为data属性的泛型类型

class BaseResp<D> {var errorCode: Int = -1var errorMsg: String = ""var data: D? = nullvar responseState: ResponseState? = null //请求状态enum class ResponseState {REQUEST_START,REQUEST_SUCCESS,REQUEST_FAILED,REQUEST_ERROR}
}
// PS : 我自己的练习Demo中, 我把T改成了D,好方便我自己理解, D = data

lib_common:RetrofitManager

common模块下的Retrofit管理类(单例模式)
这个没太多可以说的, Retrofit的使用不是很复杂

package com.example.lib_common.netimport android.util.Log
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit/*** @author: tiannan* @time: 2023/6/1.* @email: tianNanYiHao@163.com* @descripetion: 此处添加描述*//*** 用object 关键字,单例模式*/
object RetrofitManager {const val BASE_URL = "https://www.wanandroid.com/"private lateinit var retrofit: Retrofit//initinit {// 日志拦截器var loggingInterceptor = HttpLoggingInterceptor {Log.d("loggingInterceptor: ", it.toString())}.setLevel(HttpLoggingInterceptor.Level.BODY)// 配置Retrofit// 创建clientvar client: OkHttpClient = OkHttpClient().newBuilder().callTimeout(10, TimeUnit.SECONDS).connectTimeout(10, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).retryOnConnectionFailure(true).followRedirects(false)//此处暂时不做cookie支持,后续在添加//.cookieJar().addInterceptor(loggingInterceptor).build()// 创建 retrofit 实例retrofit = Retrofit.Builder().client(client).baseUrl(BASE_URL)//添加Gson解析支持.addConverterFactory(GsonConverterFactory.create()).build()}/*** Retrofit 结合 API泛型 ,创建接口实例并返回** 入参: API泛型,代指各模块的 retrofit 接口API ,如HomeApi,MyApi等接口* 返回值: API泛型的接口实例,** 备注: 由于项目中使用了koin 依赖注入* 所以,可以直接把 create 通过koin的module挂载,* 然后用到的时候, 直接通过参数注入到其他类中..*/fun <API> create(api: Class<API>): API {return retrofit.create(api)}}

lib_home: HomeApi

回到home模块,编写retrofit的Api接口

就拿Home页面的获取Banner接口来举例,我们定义 一个名为HomeApi的interface接口, 使用retrofit风格去编写
这里,我们给fun 添加 suspend, 表示这是挂起函数

package com.example.lib_home.apiimport com.example.lib_common.net.BaseResp
import com.example.lib_home.bean.Banner
import retrofit2.http.GET/*** @author: tiannan* @time: 2023/6/2.* @email: tianNanYiHao@163.com* @descripetion: 此处添加描述*/
interface HomeApi {//首页banner@GET("banner/json")suspend fun getBanner():BaseResp<List<Banner>>}

这里插一个经验总结:
在HomeApi的代码内, 想引入 import retrofit2.http.GET,但是失败
按道理说,在lib_common中已经添加了对 retrofit2dependencies

   //oKHttpimplementation("com.squareup.okhttp3:okhttp:4.9.0")implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")implementation("com.squareup.retrofit2:retrofit:2.9.0")implementation("com.squareup.retrofit2:converter-gson:2.9.0")

lib_home模块中,应该可以直接导入 import retrofit2.http.GET才对,但实际情况是
retrofit2lib_common中正常使用, 在lib_home中无法引入, 想到之前看过 implementationapi的区别, 发现果然是 在这里插入图片描述
的问题, 既然lib_common是公共的依赖, 那可以把需要放开的依赖 改为 api
即:

// lib_common
dependencies {//oKHttpimplementation("com.squareup.okhttp3:okhttp:4.9.0")implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")//retrofit// 此处由于 retrofit2 需要提供给别的模块使用, 故用 api (引用传递)// implementation 则代表,只在当前模块下可以, 对外部不可见// 所以,如果要开放某个库, 需要改为api (目测这要是 api 关键字为啥叫api的原因)api("com.squareup.retrofit2:retrofit:2.9.0")implementation("com.squareup.retrofit2:converter-gson:2.9.0")}

简单来说:implementation指令,在A模块中生效, 在引用了A模块的B模块中,B无法访问implementation引入的代码,即 依赖不会传递个B模块, 改为api即可

3.2基础网络层接口测试

截止到目前, 整理一下我们已经实现的

  • lib_home : Banner
  • lib_home: HomeApi
  • lib_common: BaseResp
  • lib_common: RetrofitManager

已经满足我们实现接口请求,我们在lib_test中,进行一下测试
lib_test模块中引入 lib_common和lib_home

// lib_test中
dependencies {implementation project(":lib_common")implementation project(":lib_home")
}

添加测试代码

//lib_test
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)var job: Job = GlobalScope.launch {var ress: BaseResp<List<Banner>> =RetrofitManager.create(HomeApi::class.java).getBanner()Log.d("TestActivity:getBanner", ress.errorMsg + "")Log.d("TestActivity:getBanner", ress.errorCode.toString())Log.d("TestActivity:getBanner", ress.data.toString())Log.d("TestActivity:getBanner", ress.data?.get(0)?.imagePath + "")}}
}

记得要去项目更目录中gradle.properties中把use_lib_test配置模式修改一下
在这里插入图片描述
(这里我对每个模块做了单独开关,方便灵活切换测试,以前使用一个useLib的tag, 不太灵活)
然后编译运行 lib_test,记得在清单文件中添加网络权限

在这里插入图片描述

测试结果:
在这里插入图片描述
不过,不推荐在lib_test中以com.android.application模式进行测试

1.要给lib_test配置清单文件,创建MainActivity
2.要注意在清单文件中,处理网络层权限等各种配置

所以如果是简单数据测试,用lib_test模块可以的, 要是UI测试的, 还是放app工程
(我这么干是因为跑起来有个lib_test的APP入口好看…好装逼…)

3.3 基础网络层优化-koin依赖注入框架

基础的网络请求有了,按理说数据已经可以获取了,但是既然要学习项目,那肯定要继续深入优化

简单说一下依赖注入

  • 无注入的常规方法
fun abc(){val abc = ABC()// use abc can do somethind
}
  • 有注入的方法
fun abc(private val abc:ABC){// use abc can do somethind
}

总之,在没有依赖的情况下,调用abc函数, 需要调用者自己主动传入 abc对象
那就意味着你必须:

  • 要持有abc对象(强耦合)
  • 要自己提供abc对象或者传递abc
  • 一旦abc需要修改,要修改多处调用

相对的,如果使用依赖注入

class AAA(private val abc:ABC){fun abc(){this.abc.toString()// use abc can do somethind}
}

在AAA类的申明中, abc对象是通过注入的方式提供

那有人会问了, 初始化AAA的过程中,不还是要传入abc对象么,这有啥区别?

依赖注入,依赖注入, 既然已经有了注入的概念, 那肯定得有依赖
别的依赖注入框架我还没有学习到, 这里拿koin这个来先回答上面的疑问, 在koin的koinApplication中,会保存有abc实例对象, 所以只要 用koin的方式获取AAA类对象实例, 那么koin会自动把abc对象注入到AAA的实例中去,从而实现了依赖

举列代码就是:

// koin方式获取AAA类的实例 aaa, aaa实例也不需要用常规初始化方法创建,用下面方法即可
val aaa:AAA by inject()fun test(){aaa.abc()
}

如上述代码所示, 可以看到,

  • AAA类的构造函数所需的入参 abc,并不需要开发者手动创建,而是通过koin提供,或者说通过依赖获得
  • 借助koin : AAA对象实例aaa, 也不需要开发者手动实例化,直接通过inject()注入

koin的 无代理、无代码生成、无反射特点, 应该能体会到一些了
(依赖注入框架有很多,但是初学者对koin的上手程度相对友好)

koin的简单理解

Koin是一个依赖注入的框架。其接口可以使用DSL的形式呈现
koin的核心部分

  • KoinApplication - 提供一个容器,用于容纳实例化的对象,便于全局使用
  • Module -是类似配置文件,用于描述注入的内容对象
    koind的通常用法:
    startKoin{}(在 Application或自定义Application类中创建)创建KoinApplication,并且将其挂载到GlobalScope中,便于通过协程使用

koin的简单使用演示

  • startKoin - koinApplication的初始化
//app 工程的application类中初始化 koin, 
// 这里仅先关注 homeModule, 其他的module,类似homeModuleprivate fun initKoin() {startKoin {androidLogger(level = Level.NONE)androidContext(this@MyApplication)modules(homeModule, projectModule, playgroundModule, myModule, userModule)}}
  • Module的创建
    homeModule,myModule这里都是对应lib_home lib_my的module
    homeModule举例子
//lib_home
val homeModule = module {// 获取ABC的实例, 单例模式,将被koinApplicaion挂载// single单例关键字,提供唯一的 ABC实例对象,如 `abc`single { ABC() }// 注入测试类-测试// factory工厂关键字, 每次都创建一个 新的HomeKoinTest实例factory {//这里, HomeKoinTest类的构造函数 ,需要入参 `abc`实例对象.// 我们只需要传入 get(), 就可以了, 这就是依赖注入里面的依赖二字的含义吧// koin会帮我们把 上面 ABC()的单例对象通过 get()依赖,注入给HomeKoinTest类HomeKoinTest(get())}
}
  • ABC类
    ABC类就一个val属性 name, 用于演示
package com.example.lib_home.koinimport android.util.Log/*** @author: tiannan* @time: 2023/6/5.* @email: tianNanYiHao@163.com* @descripetion: 此处添加描述*//*** ABC类,* 用于演示 koin**/// lib_home
class ABC {val name: String = "abc"init {Log.d("object_abc", name)}
}
  • HomeKoinTest
    (这里我把上面的AAA类,换成了KHomeKoinTest类,应该不影响理解)
package com.example.lib_home.koinimport android.util.Log
import com.example.lib_common.util.ToastUtil/*** HomeKoinTest -lib_home模块下,对koin的测试类* 依赖注入了 ABC的实例对象 abc*/
class HomeKoinTest(private val abc: ABC) {fun hi() {Log.d("home_hi", "home_hi: " + this.abc.name)ToastUtil.showShort("home_hi: " + this.abc.name)}
}
  • HomeKoinTest类的引入及测试调用
    // 在MainActivity注入 homeKoinTest, 测试效果val homeKoinTest: HomeKoinTest by inject<HomeKoinTest>()fun load() {homeKoinTest.hi()}
  • 测试结果
    在这里插入图片描述

3.4 回到APP源码,解析 真正的homeModule

一路顺下来, 我们也自己定义了homeModule,并且能够简单使用了

从现在开始,可以去 WanAndroid基础款(MVVM+Kotlin+Jetpack+组件化)

中的 lib_home/di/HomeModule.kt中看看了

val homeModule = module {single { RetrofitManager.getService(HomeApi::class.java) }single { HomeRepo(get()) }viewModel { HomeViewModel(get()) }
}

经过上文的讲解, 再看源码的 homeModule应该非常好理解

RetrofitManager我们已经在 3.1基础网络层搭建中实现过了
所以
第一行代码

single { RetrofitManager.getService(HomeApi::class.java) }

就是向koinApplication中挂载了 RetrofitManager的实例

那么,谁会向koin依赖它(RetrofitManager)呢?

很显然看谁get()

查看 第二行代码发现

single { HomeRepo(get()) }

发现 HomeRepo类的构造函数,注入了 api: HomeApi,即泛型 API

class HomeRepo(private val api: HomeApi) : BaseRepository() {}

(RetrofitManager的build返回值, 请自行查看3.1中的基础网络层RetrofitManager类)

至于第三行代码
HomeViewModel也被注入依赖(get())

   viewModel { HomeViewModel(get()) }

我们直接查看 HomeViewModel发现

class HomeViewModel(private val repo: HomeRepo) : BaseViewModel() {}

原来HomeViewModel中被注入的依赖是 repo:HomeRepo

再去查看HomeViewModel是怎么用的

    private val homeViewModel: HomeViewModel by viewModel()private fun getHomeData() {homeViewModel.getBanner()homeViewModel.getArticle(0)}

很显然

  • HomeAPI是获取网络数据的接口,它负责从服务器获取数据并返回
  • HomeRepo是数据仓库的意思, 它依赖注入了HomeApi,主要负责数据的装载
  • HomeViewModel是VM层,它依赖注入了HomeRepo,主要负责处理业务逻辑,从HomeRepo数据仓库要数据并结合liveData,做数据的绑定等工作

至此,整个Retrofit+协程+koin依赖注入+viewModel的核心逻辑已经梳理完成了
(个人觉得理顺这一套之后,整个APP的业务逻辑层面应该不是阻碍了, lib_home,lib_my等模块都是这样设计的)

3.5 结合 ViewModel 与协程,再看数据层处理

上面提到了HomeRepoHomeViewModel
他们之间的关系也清楚了:HomeApi->HomeRepo->HomeModel
但是不可否认的是,源码中已经封装的很好了,但是对于思路的推导, 还得一步步来

HomeViewModel的简单实现

抛开HomeRepo不谈, 直接让HomeViewModel依赖注入HomeApi,我们可以这样写

class HomeViewModel(private val api: HomeApi) : ViewModel() {var bannerList = MutableLiveData<List<Banner>>()fun getBanner() {viewModelScope.launch {var res: BaseResp<List<Banner>> = api.getBanner()// 对bannerList 赋值bannerList.value = res.data}}
}

HomeFragment里面, 直接就可以进行数据的获取了, 基本上如果要求不高, 整个数据层面的封装就可以到此为止了.

    private val homeViewModel: HomeViewModel by inject<HomeViewModel>()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)homeViewModel.getBanner()homeViewModel.bannerList.observe(this) {var banner = it[0]ToastUtil.showShort(banner.url)}}

HomeRepo的加入

为了优化viewmodel对数据的获取,源码中添加了Repo类,来隔离viewmodel与Retrofit,
同时也为了Repo的模块化, 又在lib_common中增加了BaseRepository基类

// 类型别名 netBlock<D>, 为了写代码简约点
typealias netBlock<D> = suspend () -> BaseResp<D>class BaseRepo {/*** 数据仓库基类 - load函数* 入参:netBlock<D> , 实际为 suspend () -> BaseResp<D> 类型的函数入参 (返回BaseResp<D>数据的挂载函数)* 入参:vmData: 类型为  MutableLiveData<D> 的 viewModel 数据, 可以理解为就是用于给VM赋值的*/suspend fun <D> load(block: netBlock<D>, vmData: MutableLiveData<BaseResp<D>>) {var result = MutableLiveData<BaseResp<D>>()result.value?.responseState = BaseResp.ResponseState.REQUEST_STARTvmData.value = result.valuetry {// 执行 网络请求result.value = block.invoke()// 网络请求状态处理when (result.value?.errorCode) {Constants.HTTP_SUCCESS -> {result.value?.responseState = BaseResp.ResponseState.REQUEST_SUCCESS}Constants.HTTP_AUTH_INVALID -> {result.value?.responseState = BaseResp.ResponseState.REQUEST_FAILEDToastUtil.showShort("认证过期,请重新登录!")// TODO: 添加路由跳转到登录页,ARouter未添加}else -> {result.value?.responseState = BaseResp.ResponseState.REQUEST_FAILEDToastUtil.showShort("code:" + result.value?.errorCode.toString() + " / msg:" + result.value?.errorMsg)}}} catch (e: Exception) {when (e) {is UnknownHostException,is HttpException,is ConnectException-> {ToastUtil.showShort("网络错误!")}else -> {ToastUtil.showShort("未知异常!")}}result.value?.responseState = BaseResp.ResponseState.REQUEST_ERROR} finally {vmData.value = result.value}}}

简单来说, BaseRepo就做了两件事

// 执行 网络请求result.value = block.invoke()

 finally {// 给 viewModel的属性赋值vmData.value = result.value}

此时,在lib_home模块中, 可以添加 HomeRepo
它依赖注入的 自然是 HomeApi

//lib_home
class HomeRepo(private val api: HomeApi) : BaseRepo() {suspend fun getBanner(vmData: MutableLiveData<BaseResp<List<Banner>>>) {load({ api.getBanner() }, vmData)}}

HomeViewModel的最终调用

class HomeViewModel(private val repo: HomeRepo) : BaseViewModel() {var bannerList = MutableLiveData<BaseResp<List<Banner>>>()fun getBanner() {
//        viewModelScope.launch {
//            repo.getBanner(bannerList)
//        }// or - 通过BaseViewModel 基类 抽取    viewModelScope.launch {}launch { repo.getBanner(bannerList) }}
}

HomeFragment的调用及测试

(记得要做数据判空…)

    private val homeViewModel: HomeViewModel by inject<HomeViewModel>()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)homeViewModel.getBanner()homeViewModel.bannerList.observe(this) {val url = it?.data?.get(0)?.urlToastUtil.showShort(url.toString())}}

在这里插入图片描述
至此,整个数据层的封装已经基本OK
这个套路掌握之后,我们也可以自己尝试进行更改,优化.


http://www.ppmy.cn/news/242313.html

相关文章

【无标题】医疗信息管理系统MIMS(Java+MySQL+JDBC)

项目源码在这里: MIMS 一点东西 在Java项目中&#xff0c;可以使用System.exit(0)代码执行来结束整个项目。 System.exit(0)是Java中的一个方法&#xff0c;其作用是退出Java虚拟机。调用该方法时&#xff0c;Java虚拟机会立即关闭并结束程序的运行&#xff0c;因此可以通过…

MuMu 模拟器 上保证能不闪退的知乎最新版本 -- 已经测试成功

MuMu 浏览器版本号 1.5.3 &#xff08;20181105&#xff09;系统为 MacOS 10.13.6 Zhihu 版本号 v5.21.1 - 发布日期2018-07-13 支持&#xff1a; 查看盐值查看徽章

uniapp 运行模拟器 (MUMU)

使用MuMu模拟器 显示都为正常启动 但是就一直处于同步手机程序文件完成&#xff0c;不显示项目内容&#xff0c;直接不动&#xff0c;重启hbx和MuMu都不行 不知道什么问题 &#xff0c;有大佬能解决吗

Android原生模拟器预装三方app

1.开启模拟器 开启模拟器不能通过as开启,需要通过命令启动,进入sdk模拟器所在路径,-avd后面的参数是你模拟器的名字 emulator.exe -avd honda3.0 -writable-system -no-snapshot-load 2.获取root权限及remount权限 adb root adb shell avbctl disable-verification adb reb…

android模拟器登录用户,如何在模拟器上登录小米账号的游戏

电脑上如何用安卓模拟器登录小米手机游戏&#xff1f; 这个很简单&#xff0c;电脑上下载逍遥模拟器&#xff0c;直接可以登录问道手游&#xff0c;帐号和手机互通的。如果你想要玩手机上已经有的帐号&#xff0c;就需要在逍遥模拟器里面下载你手机上的端&#xff0c;逍遥模拟器…

adb连接网易mumu模拟器

adb连接mumu模拟器 mumu模拟器开启调试模式 在命令行&#xff08;cmd&#xff09;输入 adb connect 127.0.0.1:7555 该图提示doesn’t match this client &#xff0c;所以没有连接上&#xff0c;所以我们要找到mumu模拟器的安装路径&#xff0c;找到adb的路径并进到该路径 …

【AndroidStudio】使用网易 MUMU 模拟器调试

【介绍】网易MuMu&#xff08;安卓模拟器&#xff09;&#xff0c;是网易官方推出的精品游戏服务平台&#xff0c;安装后可在电脑上运行各类游戏与应用&#xff0c;具备全面兼容、操作流畅、智能辅助等特点。 官网下载&#xff1a;http://mumu.163.com/ 推荐指数&#xff1a;…

【Android】使用第三方模拟器-网易MUMU

文章目录 AMD不能使用AS自带模拟器网易MUMU模拟器Genymotion模拟器 AMD不能使用AS自带模拟器 所以得下第三方模拟器。 网易MUMU模拟器 下载完后把MUMU模拟器打开 然后在命令行中使用adb命令连接 adb connect 127.0.0.1:7555连接成功&#xff1a; Genymotion模拟器 收费了

Android开发之模拟器

1.转载&#xff1a;模拟器包含这些&#xff1a; https://blog.csdn.net/fuhanghang/article/details/96728205?ops_request_misc%257B%2522request%255Fid%2522%253A%2522158739057719726867834646%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&requ…

mumu的adb_如何使用网易mumu模拟器调试安卓程序?

如何使用网易mumu模拟器调试安卓程序&#xff1f; 使用AndroidStudio编写安卓程序的时候&#xff0c;往往会使用到模拟器&#xff0c;但AS自带的模拟器占用内存大&#xff0c;运行速度慢等等问题&#xff0c;所以就在网上找了几个当前比较流行的模拟器。安装调试对比之后&#…

mumu模拟器支持android版本,MuMu安卓模拟器官网下载_网易MuMu手游助手_手机模拟器_手游模拟器...

平安世界绮丽万千&#xff0c;百鬼物语奇特诡秘。《阴阳师》自上线以来&#xff0c;其唯美的平安世界、神秘的妖怪式神、曲折离奇的故事情节都十分受玩家的欢迎。如今美之世界跨界降临&#xff01;在网易MuMu模拟器的技术支持下&#xff0c;《阴阳师》Mac系统桌面版突破重重限制…

mumu模拟器使用

文章目录 AS中安装mumu模拟器断开连接在mumu模拟器中安装本地apk屏幕旋转设置设置网络代理蓝牙安装apk更多使用说明 AS中安装mumu模拟器 下载 http://mumu.163.com/ 下载完成后运行 打开AS Terminal adb connect 127.0.0.1:7555运行程序 有mumu模拟器了 断开连接 adb d…

Android自带模拟器的安装

在这里记载下Android模拟器的安装&#xff0c;由于觉得自己电脑还比较不错&#xff0c;能跑得起自带的模拟器&#xff0c;这里就介绍下Android自带的模拟器安装。网上的Genymotion之前旧电脑上也装过&#xff0c;等以后要装的时候再写一篇记录下方法。 想运行AVD&#xff0c;之…

Android Emulator 模拟器使用方法

1、Android模拟器介绍 Android中提供了一个模拟器来模拟ARM核的移动设备。Android的模拟器是基于QEMU开发的&#xff0c;QEMU是一个有名的开源虚拟机项目(详见http://bellard.org/qemu/)&#xff0c;它可以提供一个虚拟的ARM移动设备。Android模拟器被命名为goldfish&#xff0…

【移动端二】模拟器

夜神模拟器连接Airtest自动化测试 下载Airtest, 下载夜神模拟器/木木模拟器&#xff0c; 全部安装打开Airtest&#xff0c; 连接模拟器。 模拟器 adb连接代码 网易Mumu adb connect 127.0.0.1:7555 夜神 adb connect 127.0.0.1:62001 逍遥 adb connect 127.0.0.1:21503 iTool…

Android检测模拟器

1. 前言 模拟器大家都应该很熟悉的吧&#xff01;现在市面上的模拟器多的数不清&#xff0c;例如&#xff1a;雷电模拟器、夜神模拟器等等。而因为模拟器所搭载的Android系统是阉割过的&#xff0c;一些安全相关的功能都没有了。这就造成了一个安全问题&#xff0c;在模拟器运…

Redis实现分布式锁的几种方案

文章目录 1.前言1.1分布式锁1.2分布式锁的几种方案1.3Redis分布式锁需要满足的条件 2.Redis实现分布式锁的几种方案2.1 SETNX EXPIRE2.2 SETNX value&#xff08;系统时间过期时间&#xff09;2.3 通过开源框架-Redisson 1.前言 对于Redis实现分布式锁的几种方案这个话题&am…

Terminal Emulator for Android(安卓终端模拟器)的使用

2020-03-10 Update: Terminal Emulator for Android这个Android 4时代的app看起来停止维护了。在今天想要体会本文所描述的东西&#xff0c;不妨安装Termux。Termux不仅预置busybox&#xff0c;还提供apt这个强大的包管理器&#xff0c;简直就像一个Linux发行版。 网络上关于介…

Android-4.4模拟器

0 前言 本文重点说明如何在Ubuntu14.04系统中使用Android-4.4.4模拟器。 1 模拟器&镜像&皮肤 编译Android源码的过程详见参考资料[2]第1.1小节&#xff0c;模拟器用到的工具和镜像如下&#xff1a; prebuilts/qemu-kernel/arm有kernel-qemu-armv7等内核镜像developme…
最新文章