如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(五)完结

RK3572-Android16-记录一次OTA升级失败解决方案 这里记录一次解决RK3576 Android16 OTA 用Recovery 升级失败的一次解决案例,将部分经验作为总结。本次OTA升级耗时了几天,最终是RK给出的解决方案,再加上经验来解决了其它部分问题新平台,高一点的Android版本,可能本身有bug,多请教平台来,不要蒙着干,干不出来的。 阅读详情

前期回顾


如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(一)基础搭建

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(二)音乐列表

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(三)播放能力

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(四)登录注册

前言


在这里插入图片描述

本章继续 Jetpack 全家桶实战音乐播放器;

协程


上一章,我们用 RxJava + Retrofit 实现了网络请求,本章我们用协程实现一版本;

API

首先,让我们的 API 来支持协程;

interface WanAndroidAPI {

    /**
     * 登录API
     */
    @POST("/user/login")
    @FormUrlEncoded
    suspend fun loginActionCoroutine(@Field("username") username: String,
                                     @Field("password") password: String)
            : LoginRegisterResponseWrapper<LoginRegisterResponse> // 返回值

    /** 
     * 注册的API
     */
    @POST("/user/register")
    @FormUrlEncoded
    suspend fun registerActionCoroutine(@Field("username") username: String,
                                        @Field("password") password: String,
                                        @Field("repassword") repassword: String)
            : LoginRegisterResponseWrapper<LoginRegisterResponse> // 返回值
}

实现很简单,只需要使用 suspend 关键字修饰即可,这样 Retrofit 就能自动帮我们进行线程的切换了;

HttpReqeustManager

suspend fun loginCoroutine(
    username: String,
    password: String)
=
    APIClient.instance.instanceRetrofit(WanAndroidAPI::class.java)
        .loginActionCoroutine(username, password).data

这里我们就不需要使用 RxJava 来处理线程切换以及自定义 Response 处理数据了

RequestLoginViewModel

ViewModel 中我们直接调用 loginCoroutine方法;

class RequestLoginViewModel : ViewModel() {

    .... 省略部分代码

    // 协程函数
    fun requestLoginCoroutine(context: Context, username: String, userpwd: String) {

        // GlobalScope(Dispatchers.Main) 全局作用域 默认是异步线程
        // viewModelScope.launch 默认是主线程 == (Dispatchers.Main)
        viewModelScope.launch {
            // 当前是主线程,可以弹框
            LoadingDialog.show(context)
            // 左边的是:主线程,右边:异步线程
            val dataResult =  HttpRequestManager.instance.loginCoroutine(username, userpwd)
            // 当前是主线程,可以用 setValue 更新状态
            if (dataResult != null) {
                loginData1?.value = dataResult
            } else {
                loginData2?.value = "登录失败,发送了意外,请检查用户名与密码 或者你的代码"
            }
            // 当前是主线程,可以取消弹框
            LoadingDialog.cancel()
        }
    }
}

因为继承了 ViewModel,所以我们可以直接使用 viewModelScope 来启动一个协程;

Activity 中直接调用这个 requestLoginCoroutine方法;

requestLoginViewModel?.requestLoginCoroutine( this@LoginActivity,
    loginViewModel?.userName?.value!!, loginViewModel?.userPwd?.value!!)

这里要注意 Retrofit 的版本号,要使用支持协程的版本

SearchFragment


我们接下来完善『搜索』模块;

fragment_search.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="click"
            type="com.xiangxue.puremusic.ui.page.SearchFragment.ClickProxy" />

        <variable
            name="vm"
            type="com.xiangxue.puremusic.bridge.state.SearchViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black">

        <net.steamcrafted.materialiconlib.MaterialIconView
            android:id="@+id/btn_back"
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_marginStart="12dp"
            android:layout_marginTop="37dp"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:onClick="@{()->click.back()}"
            android:scaleType="center"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:materialIcon="arrow_left"
            app:materialIconColor="@color/white"
            app:materialIconSize="28dp" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:layout_marginTop="37dp"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:text="@string/relearn_android"
            android:textSize="18sp"
            android:textStyle="bold"
            android:textColor="@color/white"
            app:layout_constraintBottom_toTopOf="@+id/tv_content"
            app:layout_constraintLeft_toRightOf="@+id/btn_back"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="18dp"
            android:layout_marginBottom="4dp"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:text="@string/learn_more_friends"
            android:textColor="@color/white"
            android:textSize="12sp"
            app:layout_constraintLeft_toRightOf="@+id/btn_back"
            app:layout_constraintTop_toBottomOf="@+id/tv_title" />

        <TextView
            android:id="@+id/btn_subsribe"
            drawable_radius="@{25}"
            drawable_solidColor="@{0xffFF7055}"
            android:layout_width="100dp"
            android:layout_height="32dp"
            android:layout_marginTop="37dp"
            android:layout_marginEnd="20dp"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:gravity="center"
            android:onClick="@{()->click.subscribe()}"
            android:text="@string/learn_more"
            android:textColor="@color/white"
            android:textSize="13sp"
            android:textStyle="bold"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <net.steamcrafted.materialiconlib.MaterialIconView
            android:id="@+id/ic"
            android:layout_width="242dp"
            android:layout_height="242dp"
            android:layout_marginTop="24dp"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:onClick="@{()->click.testNav()}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_content"
            app:materialIcon="magnify"
            app:materialIconColor="@color/white"
            app:materialIconSize="28dp" />

        <TextView
            android:id="@+id/tv_tip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="?attr/selectableItemBackground"
            android:onClick="@{()->click.testNav()}"
            android:text="@string/search_page_tip"
            android:textSize="16sp"
            android:visibility="gone"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/ic" />
        <TextView
            android:id="@+id/tv_test_download"
            drawable_radius="@{25}"
            drawable_solidColor="@{0xffFF7055}"
            android:layout_width="wrap_content"
            android:layout_height="32dp"
            android:background="?attr/selectableItemBackground"
            android:gravity="center"
            android:onClick="@{()->click.testDownload()}"
            android:paddingLeft="12dp"
            android:paddingRight="12dp"
            android:text="@string/test_download"
            android:textColor="@color/white"
            android:textSize="13sp"
            android:textStyle="bold"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_tip" />

        <TextView
            android:id="@+id/tv_test_lifecycle_download"
            drawable_radius="@{25}"
            drawable_solidColor="@{0xffFF7055}"
            android:layout_width="wrap_content"
            android:layout_height="32dp"
            android:layout_marginTop="12dp"
            android:background="?attr/selectableItemBackground"
            android:gravity="center"
            android:onClick="@{()->click.testLifecycleDownload()}"
            android:paddingLeft="12dp"
            android:paddingRight="12dp"
            android:text="@string/test_lifecycle_download"
            android:textColor="@color/white"
            android:textSize="13sp"
            android:textStyle="bold"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_test_download" />

        <!-- 频繁更新 刷新 -->
        <SeekBar
            android:id="@+id/pb"
            android:layout_width="0dp"
            android:layout_height="20dp"
            android:layout_marginLeft="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginRight="16dp"
            android:background="@color/white"
            android:progress="@{vm.progress}"
            android:progressDrawable="@drawable/progressbar_color"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_test_lifecycle_download" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

布局很简单~

SearchViewModel

class SearchViewModel : ViewModel() {

    // LiveData 还是选择 DataBinding 的 ObservableField
    // 使用的是 DataBinding 的 ObservableField
    // 1.更新很频繁,因为要把进度更新到拖动条
    // 2.界面可见和不可见,都必须执行,所以不能用 LiveData
    @JvmField
    val progress = ObservableField<Int>()
}

状态 ViewModel 用来观察下载进度;

DownloadViewModel

用来模拟下载的 ViewModel;

class DownloadViewModel : ViewModel() {

    var downloadFileLiveData: MutableLiveData<DownloadFile>? = null
        get() {
            if (field == null) {
                field = MutableLiveData<DownloadFile>()
            }
            return field
        }
        private set

    // 请求仓库 模拟下载功能
    fun requestDownloadFile() = 
        HttpRequestManager.instance.downloadFile(downloadFileLiveData)
}

这里就是调用 downloadFile方法来模拟下载;

downloadFile

fun downloadFile(liveData: MutableLiveData<DownloadFile>?) {
    val timer = Timer()
    val task: TimerTask = object : TimerTask() {
        override fun run() {
            //模拟下载,假设下载一个文件要 10秒、每 100 毫秒下载 1% 并通知 UI 层
            var downloadFile = liveData?.value
            if (downloadFile == null) {
                downloadFile = DownloadFile()
            }
            if (downloadFile.progress < 100) {
                downloadFile.progress += 1
                Log.d("TAG", "下载进度 " + downloadFile.progress + "%")
            } else {
                timer.cancel()
                downloadFile.progress = 0
                return
            }
            if (downloadFile.isForgive) {
                timer.cancel()
                downloadFile.progress = 0
                return
            }
            liveData?.postValue(downloadFile)
            downloadFile(liveData)
        }
    }
    timer.schedule(task, 100)
}

接下来,在 Framgent 中初始化 ViewModel 和 DataBinding

SearchFragment

class SearchFragment  : BaseFragment(){

    private var mBinding: FragmentSearchBinding? = null
    private var mSearchViewModel: SearchViewModel? = null // 搜索界面 相关的 VM
    private var mDownloadViewModel: DownloadViewModel? = null  // 下载相关的 VM

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mDownloadViewModel = getActivityViewModelProvider(mActivity!!).get(DownloadViewModel::class.java)
        mSearchViewModel = getFragmentViewModelProvider(this).get(SearchViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val view: View = inflater.inflate(R.layout.fragment_search, container, false)
        mBinding = FragmentSearchBinding.bind(view)
        mBinding?.click = ClickProxy() // 设置监听
        mBinding?.vm = mSearchViewModel // 设置 自身VM // todo Status ViewModel
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 就是不需要Lifecycle观察 宿主生命周期变化
        mDownloadViewModel?.downloadFileLiveData?.observeForever({
            // 修改了 StatusViewModel
            // 让自身的VM 数据发送变化, 那么布局就会感应变化
            mSearchViewModel?.progress?.set(it.progress)
        })
    }

    inner class ClickProxy {

        private val PATH2 = "http://www.baidu.com/" // 网页

        // 跳转 加载一个网页
        fun testNav()  = startPath()
        // 跳转 加载一个网页
        fun subscribe() = startPath()
        // 返回
        fun back() {
            nav().navigateUp() // back键的时候,返回上一个界面
        }

        private fun startPath() {
            val uri = Uri.parse(PATH2)
            val intent = Intent(Intent.ACTION_VIEW, uri)
            startActivity(intent)
        }

        // 测试下载,返回页面依然有效
        fun testLifecycleDownload() {
            mDownloadViewModel ?.requestDownloadFile()
        }

        // 测试下载,返回页面依然有效
        fun testDownload() {
            mDownloadViewModel ?.requestDownloadFile()
        }
    }
}

DrawerFragment


接下来,晚上侧边栏 Framgent;

首先来完成 xml,fragment_drawer.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="vm"
            type="com.xiangxue.puremusic.bridge.state.DrawerViewModel" />

        <variable
            name="click"
            type="com.xiangxue.puremusic.ui.page.DrawerFragment.ClickProxy" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black">

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/iv_logo"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginTop="40dp"
            android:onClick="@{()->click.logoClick()}"
            android:src="@drawable/ic_launcher"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_app"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:background="?attr/selectableItemBackground"
            android:onClick="@{()->click.logoClick()}"
            android:text="@string/app_name"
            android:textColor="@color/white"
            android:textSize="20sp"
            android:textStyle="bold"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/iv_logo" />

        <TextView
            android:id="@+id/tv_summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:background="?attr/selectableItemBackground"
            android:onClick="@{()->click.logoClick()}"
            android:text="@string/app_summary"
            android:textColor="@color/white"
            android:textSize="12sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_app" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginTop="24dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toTopOf="@+id/tv_copyright"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_summary" />

        <TextView
            android:id="@+id/tv_copyright"
            android:layout_width="0dp"
            android:layout_height="48dp"
            android:background="?attr/selectableItemBackground"
            android:gravity="center"
            android:onClick="@{()->click.logoClick()}"
            android:text="@string/Copyright"
            android:textColor="@color/white"
            android:textSize="12sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/rv" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

还是之前的规则,我们需要两个 ViewModel,一个是状态 ViewModel,一个是业务 ViewModel

InfoRequestViewModel

class InfoRequestViewModel : ViewModel() {

    var libraryLiveData: MutableLiveData<List<LibraryInfo>>? = null
        get() {
            if (field == null) {
                field = MutableLiveData()
            }
            return field
        }
        private set

    fun requestLibraryInfo() {
        // 调用仓库
        HttpRequestManager.instance.getLibraryInfo(libraryLiveData)
    }
}

DrawerViewModel

class DrawerViewModel : ViewModel() {
    // todo
}

DrawerFragment

class DrawerFragment : BaseFragment(){

    private var mBinding: FragmentDrawerBinding? = null
    private var mDrawerViewModel: DrawerViewModel? = null // Status ViewModel 独一份
    private var infoRequestViewModel: InfoRequestViewModel? = null // Request ViewModel

    private var mAdapter: SimpleBaseBindingAdapter<LibraryInfo?, AdapterLibraryBinding?>? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        mActivity = context as AppCompatActivity
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mDrawerViewModel = getFragmentViewModelProvider(this).get(DrawerViewModel::class.java)
        infoRequestViewModel = getFragmentViewModelProvider(this).get(InfoRequestViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View { // DataBinding 与 ViewModel关联
        val view: View = inflater.inflate(R.layout.fragment_drawer, container, false)
        mBinding = FragmentDrawerBinding.bind(view)
        mBinding?.vm = mDrawerViewModel
        mBinding?.click = ClickProxy() // 点击
        return view
    }

    // 获取 Item数据集合
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        mAdapter = object : SimpleBaseBindingAdapter<LibraryInfo?, AdapterLibraryBinding?>(
            context,
            R.layout.adapter_library
        ) {

            override fun onSimpleBindItem(
                binding: AdapterLibraryBinding?,
                item: LibraryInfo?,
                holder: RecyclerView.ViewHolder?
            ) {
                // 把数据 设置好,就显示数据了
                binding?.info = item
                binding?.root?.setOnClickListener {
                    Toast.makeText(mContext, "哎呀,还在研发中...", Toast.LENGTH_SHORT).show()
                }
            }
        }

        // 设置适配器 到 RecyclerView
        mBinding?.rv?.adapter = mAdapter
        infoRequestViewModel?.libraryLiveData?.observe(viewLifecycleOwner, { libraryInfos ->
            mAdapter?.list = libraryInfos
            mAdapter?.notifyDataSetChanged()
        })

        // 请求数据 调用 Request 的 ViewModel 加载数据
        infoRequestViewModel?.requestLibraryInfo()
    }

    inner class ClickProxy {
        fun logoClick() = Toast.makeText(mActivity, "哎呀,你能不能不要乱点啊,程序员还在玩命编码中...", Toast.LENGTH_SHORT).show()
    }
}

好了,整体大体功能就完成了

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

QGC二次开发实战指南(一):从零构建自定义地面站界面 本文详细介绍了QGC二次开发实战指南,从零开始构建自定义地面站界面。通过QT和QML技术结合MAVLINK协议,开发者可以打造适合特定业务场景的无人机监控系统,提升操作效率与用户体验。文章包含环境搭建、界面设计、数据绑定等完整开发流程,是QGC二次开发的实用教程。 阅读详情

相关推荐

实战指南:UOS专业版命令行高效下载离线安装包及依赖

本文提供了一份UOS专业版离线安装包的实战下载指南。针对无外网环境下的软件部署难题,详细介绍了如何通过命令行精准获取软件包名,并利用`apt download`命令高效下载主程序及其所有依赖。文章重点解析了使用`apt install --dry-run`模拟安装来完整获取依赖包的方法,并给出了离线安装、问题排查及自动化脚本编写等高级技巧,帮助管理员和运维人员实现可靠的批量离线部署。

honey的博客 1109

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(四)登录注册

本文介绍了如何使用Jetpack组件和RxJava完善音乐播放器的用户注册功能。通过DataBinding实现布局与ViewModel的双向绑定,展示了注册页面的XML布局设计,包含用户名、密码输入框和注册按钮。重点介绍了RegisterViewModel的构建,使用MutableLiveData实现对用户输入数据的响应式处理,为后续登录功能开发奠定基础。这是音乐播放器系列教程的第四部分,前文已介绍基础搭建、音乐列表和播放功能实现。

IT_Android的博客 761

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(一)基础搭建

本文介绍了一个基于Jetpack组件的音乐播放器开发框架,采用MVVM架构和Kotlin语言实现。核心设计采用单Activity+多Fragment模式,通过Navigation组件管理页面导航。MainActivity作为唯一Activity,集成了DrawerLayout实现侧滑菜单和SlidingUpPanelLayout实现上下滑动面板,包含三个主要导航区域:主界面、底部播放条和侧边菜单。使用ViewModel实现数据管理,确保横竖屏切换时数据不丢失。框架充分利用了Jetpack组件,遵循Googl

IT_Android的博客 799

元数据管理:DataHub和OpenMetadata对比分析

DataHub和OpenMetadata都试图解决数据编目,搜索,发现,治理和质量方面的相同问题。这两种工具都是出于为需要支持大量数据源、团队和用例的大型组织解决这些问题而诞生的。尽管这些工具在发布历史和成熟度方面略有不同,但它们的功能存在显着重叠。

weixin_38030820的博客 6632

在 Windows 系统下使用 VMware 安装 Ubuntu 24.04 LTS 系统

Ubuntu24.04桌面端官方下载:https://cn.ubuntu.com/download/desktop。

u014695938的博客 1万+

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(二)音乐列表

本文介绍了如何使用Jetpack组件结合MVVM架构和Kotlin开发音乐播放器应用。主要内容包括: 数据填充部分,通过SharedViewModel实现状态管理,包含播放控制、菜单开关等功能的LiveData封装 MainActivity的初始化过程,包括ViewModel和DataBinding的绑定设置 使用BindingAdapter实现自定义XML属性,如DrawerLayout的开关控制 MainFragment的布局结构,采用CoordinatorLayout+AppBarLayout实现折叠

IT_Android的博客 624

[RK3588-Android12] 深入解析USB Type-C PD充电功能的DTS配置与内核驱动修改

本文深入探讨了在RK3588 Android12平台上禁用USB Type-C PD充电功能的实现方法。通过分析设备树(DTS)配置与内核驱动(TCPM框架)的联动修改,详细解读了如何通过添加`pd-disable`属性及修改`tcpm.c`驱动代码,使Type-C端口绕过PD协议协商,仅工作在传统电源模式,以满足工控等特定场景对供电稳定性和成本控制的需求。

9o8p7i6u5y的博客 992

如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(三)播放能力

本文介绍了如何使用Jetpack完善音乐播放器的播放功能。主要包含两个核心组件:PlayerManager和PlayerService。PlayerManager基于开源播放器库实现,封装了播放控制逻辑(播放/暂停/切歌/模式切换等)和播放状态管理,采用单例模式确保全局唯一。PlayerService则负责实现后台播放能力,处理电话来电等中断场景,通过PlayerCallHelper与PlayerManager交互。文中详细展示了PlayerManager的完整实现代码,包括初始化、播放控制、状态获取等方法

IT_Android的博客 479

Unity 工具 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码)

Unity 基础 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码) 目录 Unity 基础 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码) 一、简单介绍 二、实现原理 三、注意实现 四、效果预览 五、ZXing.net ( zxing.unity.dll ) 插件下载 六、实现步骤 七、关键代码 一、简单介绍 Unity 开发中,自己整理的一些游戏开发可能用到的功能,整理归档,方便游...

仙魁XAN 9824

Xilinx平台SRIO介绍(五)示例工程Example Design介绍

理解IP的使用,少不了Example Design的学习。

数字IC/FPGA杂货铺,找一找,或许有你想要的~ 1万+

2021年字节跳动+京东+美团面试总结!大牛最佳总结

大厂offer是每个技术人的追求? 我觉得每一个程序员的梦想大概就是能够找一份大厂的 Offer,我觉得这很正常,这并不是我们的饭后谈资而是每个技术人的追求。像阿里、腾讯、美团、字节跳动、京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司/小公司,如果说能够在这样的公司锻炼几年,相信对自己能力的提升还是非常大的。但不是每个人都能够进入大厂的,这往往取决于我们的能力、学历、面试表现等因素。 现在面试大公司你说你没有 Android Jetpack、协程、 Jetpack Compose 、Flutte

m0_56228922的博客 219

全新DEIM有效涨点改进目录 | 本专栏持续更新500+篇内容 | 包含各种顶会顶刊卷积、注意力、特征融合模块、有效特征聚合提取模块,上采样模块、下采样模块,二次创新模块、独家创新等几百种创新点改进

🚀【CVPR2025DEIM改进专栏速览】🔥 专栏提供500+篇目标检测创新改进方案,涵盖卷积/注意力/特征融合等创新改进点,均源自顶会论文及实战经验。支持多领域应用(工业/医疗/遥感等),配套完整教程和答疑服务。亮点包括: 1️⃣ 持续更新SOTA模型改进方案 2️⃣ 小白友好型实验操作指南 3️⃣ 跨学科科研支持(已助非计算机专业发表论文) 4️⃣ 提供虚拟环境配置方案。 订阅可获完整项目代码及交流群服务,适合研究生科研论文改进需求。价格呈上涨趋势,早订更优惠!

Ai缝合怪、助力小伙伴高效发论文 2040

安卓开发快速学习!跟我一起手写EventBus吧,已拿offer

大厂offer是每个技术人的追求? 我觉得每一个程序员的梦想大概就是能够找一份大厂的 Offer,我觉得这很正常,这并不是我们的饭后谈资而是每个技术人的追求。像阿里、腾讯、美团、字节跳动、京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司/小公司,如果说能够在这样的公司锻炼几年,相信对自己能力的提升还是非常大的。但不是每个人都能够进入大厂的,这往往取决于我们的能力、学历、面试表现等因素。 现在面试大公司你说你没有 Android Jetpack、协程、 Jetpack Compose 、Flutte

171

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

京东Android春招笔试题全解析:从基础到框架的考点拆解

在移动开发领域,Android技术栈的深度与广度始终是衡量工程师能力的重要标尺。无论是Java集合源码中的HashMap实现细节,还是并发编程中线程池的参数配置,这些基础概念构成了面试考察的核心。消息机制中Handler与Looper的协作原理,跨进程通信中Binder与AIDL的设计思想,以及性能优化中内存泄漏与ANR的排查路径,都是实际工程中反复应用的关键技术。理解它们背后的原理,不仅能帮助开发者应对笔试中的高频问题,更能支撑其解决线上复杂故障。从网络协议的握手过程到构建工具链的演进,技术价值渗透在应用

weixin_34232744的博客 444

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

2023小满春招Android笔试复盘:系统机制与代码能力全解析

在Android开发岗位的春招笔试中,系统机制理解与工程编码能力往往决定着候选人的竞争力。Binder作为跨进程通信的核心,其mmap内存映射机制保障了一次拷贝的高效传输;Handler的消息循环与同步屏障则直接关系到UI渲染的流畅度。自定义View的测量、布局、绘制流程,展现了Android框架对开发者基本功的深度要求。同时,LRU缓存、Kotlin协程、性能优化等高频考点,也反映出大厂对实际排障经验与架构设计思维的重视。本文基于2023年度小满春招Android研发岗第二批笔试的真实现场回忆,逐题拆解选

weixin_30455023的博客 365

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Android 14+ 模拟器抓包实录:把 Burp CA 装成系统 CA,让 targetSdk≥34 的 App 乖乖解密

本文记录了在 Android 14+ 环境下,通过将 Burp CA 安装为系统信任锚,成功抓取 targetSdk=35 App 的 HTTPS 明文流量的完整实操过程。针对“浏览器可用、App 不可用”的典型现象,精准识别出根本原因是系统级证书信任策略变更(用户安装的 CA 被屏蔽),而非 SSL Pinning。仅通过 adb 命令将 CA 推送至系统证书存储并重启生效,无需 Frida、Objection 或修改 APK,实现零侵入解密。全文命令均基于 Windows + MuMu 模拟器环境实测通

JIANqiao19931029的博客 341

具身智能 2026:启元人形机器人开发者生态的“安卓时刻”来了吗

一个贯穿全文的问题:人形机器人开发者生态,是否正从「封闭垂直」走向「开源平台化」?本文以开源底座的公开数据为标尺,盘点 SDK、URDF、仿真与技能分发的开放程度,并落回启元机器人的开源路径。所有关键数据均标注来源,文末附社区与学堂链接。

在北风吹起的时候加入我们的队伍 在秋雨到来的时候成为彼此的温暖 743

android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送

橘子,计算机视觉方向工程师,深耕 AIGC 与多媒体处理领域,专注技术实用化落地。本文为原创内容,转载请注明来源与作者。优快云 首发 | 版权所有 © 2026。

下载资源或查看教程前,请务必阅读置顶的《免责声明》 496

Android 14 SELinux策略深度解析

init 的全局公开约束(如 neverallow init execute_no_trans,放 public 才能约束所有分区的规则)这就是"哪些可见"的强制机制。alps_s0(MTK 工程)的 device/mediatek/sepolicy/basic/ 提供。所有分区(system/system_ext/product/vendor)都可引用。init的公开约束、跨分区共用的 type/attribute/neverallow。——vendor 的 .cil 只能引用 plat 的。

新时代农民工 102

Android 开发入门实践(二):Kotlin 空安全,从可空类型到安全访问

延续 NewsApp 项目中的新闻标题练习,记录 Kotlin 安全调用。

Easyhood的博客 276

Android 模块化收口:能力组合框架、手写 LifecycleOwner 与热修复共存

二十多个模块业务高度相似却各有差异,核心矛盾在于“统一与灵活”的平衡。通过角色接口组合(如IProjectAware、IVoiceAware)实现能力按需注入,新增功能零侵入;用模板方法锁定生命周期流程,确保顺序一致;结合@CallSuper与工厂方法,既约束关键逻辑,又保留子类自由。最终实现:骨架统一、填空自由,管得住业务,又不侵入业务。

mmsx990的博客 395

Android Message源码阅读

本文梳理了Android Handler机制中Message的核心设计:链表结构、消息复用池(sPool)及回收机制,重点解析了异步消息与同步屏障(SyncBarrier)的协同机制。当主线程需优先处理UI绘制等高优先级任务时,系统通过postSyncBarrier插入屏障,使Looper跳过普通同步消息,优先执行标记为异步的UI渲染任务,确保帧率流畅。该机制是保障Android界面响应性的关键技术之一。

u012116089的博客 182

Android中Gradle网络下载超时、更改存放位置(解决方案)

在国内开发Android应用,需要使用到一些外网才能正常访问的环境依赖。除了挂梯子,这里再提供几个常见问题的配置方案,可以已定程度减少下载超时问题。但是要注意,不同的开发环境、网络环境、时间段,出现的问题都是不可控的,遇到配置问题,建议保持耐心,多做尝试。

indeedes的博客 122

安卓部署Linux性能实测:一颗移动处理器,能否胜任日常 Linux 开发?

本文通过在一加Ace5上部署NimoteCode的ARM64 Linux环境,测试了其在CoreMark、SQLite编译及并行构建中的表现。结果显示,尽管单核性能约2.34GHz,多核扩展比达6.15×,但优化编译耗时仍高达35秒,反映移动处理器虽具备一定开发能力,却受限于文件系统开销与运行环境(如PRoot)。实际开发中,非优化构建效率较高,而并行编译可显著提速。结论:安卓手机可作为轻量级开发辅助工具,但无法替代桌面环境,真正价值在于便携性与即时响应,而非全功能替代。

一个不知名码农的专栏 282

Android 开发入门实践(九):把新闻数据与页面状态交给 ViewModel

本文记录第110—117课学习成果:将页面标题、新闻列表及选中编号逐步迁移至NewsViewModel,通过ViewModelProvider获取实例并读取数据。页面调用ViewModel方法处理选择与返回,实现状态管理解耦。过程中纠正了对Activity重建机制的误解,并厘清mutableStateOf的可观察性与ViewModel对象生命周期的区别,深化了对Compose状态更新机制的理解。

Easyhood的博客 203
上一篇: 如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(四)登录注册
下一篇: 如何应对 Android 面试官 -> 玩转 RxJava (基础使用)
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值