Android第十三次面试总结(四大 组件基础)

news2025/6/12 17:16:35

Activity生命周期和四大启动模式详解

一、Activity 生命周期

Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机:

  1. onCreate()​

    • 调用时机​:Activity 首次创建时调用。
    • 作用​:初始化布局(setContentView)、绑定数据、创建后台线程等。
    • 注意​:在此方法中应避免耗时操作。
  2. onStart()​

    • 调用时机​:Activity 可见但未获得焦点(例如被对话框覆盖)。
    • 作用​:恢复UI更新或资源加载。
  3. onResume()​

    • 调用时机​:Activity 进入前台并可与用户交互。
    • 作用​:启动动画、传感器监听、高频率更新UI等。
    • 关键点​:此时 Activity 位于栈顶。
  4. onPause()​

    • 调用时机​:Activity 失去焦点(如弹出对话框或跳转到其他 Activity)。
    • 作用​:保存临时数据、释放资源(如摄像头)。
    • 注意​:需快速执行,否则会影响新 Activity 的启动。
  5. onStop()​

    • 调用时机​:Activity 完全不可见(被其他 Activity 覆盖或退出)。
    • 作用​:停止动画、释放非必要资源。
  6. onDestroy()​

    • 调用时机​:Activity 被销毁(用户主动退出或系统回收资源)。
    • 作用​:清理内存、注销广播等。
  7. onRestart()​

    • 调用时机​:Activity 从停止状态重新回到前台(如按返回键返回)。
    • 流程​:onRestart()onStart()onResume()

场景应用

场景 1:打开新页面

流程​:

  1. 原 Activity 执行 onPause()(失去焦点)
  2. 新 Activity 依次执行:
    • onCreate()(初始化)
    • onStart()(可见)
    • onResume()(可交互)
  3. 原 Activity 执行 onStop()(完全不可见)

典型场景​:

  • 从主页跳转到详情页
  • 列表页打开新的商品页

场景 2:返回上一个页面

流程​:

  1. 当前 Activity 执行 onPause()
  2. 上一个 Activity 依次执行:
    • onRestart()(重新激活)
    • onStart()(可见)
    • onResume()(可交互)
  3. 当前 Activity 执行:
    • onStop()
    • onDestroy()(被销毁)

典型场景​:

  • 提交订单后返回购物车
  • 查看图片详情后返回相册

场景 3:屏幕旋转

流程​:

  1. onPause() → onSaveInstanceState()(保存数据)
  2. onStop() → onDestroy()(销毁实例)
  3. 重新创建:
    • onCreate()(携带保存的数据)
    • onStart()
    • onRestoreInstanceState()(恢复数据)
    • onResume()

开发重点​:

  • 在 onSaveInstanceState() 保存编辑框内容/滚动位置
  • 避免在旋转时中断网络请求

场景 4:被弹窗/来电中断
场景生命周期变化恢复顺序
普通对话框onPause()onResume()
全屏弹窗/来电onPause() → onStop()onRestart() → onResume()

关键区别​:

  • 是否完全遮挡决定是否触发 onStop()

二、四大启动模式(Launch Mode)

        Android 的四大启动模式核心基于任务栈管理机制实现。系统通过 ActivityManagerService(AMS)维护一个全局的任务栈结构,每个任务栈由 TaskRecord 对象表示,遵循后进先出的堆栈原则。当启动 Activity 时,AMS 会根据不同启动模式的预设规则,动态调整任务栈的组成。

        ​standard 模式是最基础的方式。它简单地无条件创建新 Activity 实例,并将其压入当前任务栈顶部。这种模式不涉及任何复用判断,因此可能在栈中积累多个相同 Activity 实例,对内存管理构成压力。系统在处理时直接调用 ActivityStarter 的强制压栈方法,整个过程没有任何条件检测。

适用场景​:

  • 常规页面跳转​(如商品详情页、新闻内容页)
  • 多实例并行需求​(如浏览器标签页、文档编辑页)
    典型案例​:
  • 电商应用浏览商品:主页 → 商品A页 → 商品B页,栈内存在多个不同商品页实例
  • 文档应用编辑文件:同时打开文档1文档2,返回时可逐个切换

​        singleTop 模式的核心在于栈顶检查机制。启动时,AMS 会立即判断目标 Activity 是否恰好位于当前栈顶。如果匹配成功,则直接触发该实例的 onNewIntent() 进行数据刷新,完全跳过创建流程;若不在栈顶,则回退到 standard 模式创建新实例。这种设计特别适合通知栏点击等高频重复启动的场景。

适用场景​:

  • 避免重复通知​(通知栏多次点击跳转同一页)
  • 高频操作入口​(如刷新按钮、重复提交页面)
    典型案例​:
  • 微信消息页面:当停留在聊天页A时收到新消息,点击通知直接刷新当前页面
  • 支付倒计时页:用户多次点击“重新支付”按钮,复用当前页面更新倒计时
    特殊场景​:
  • 扫二维码页面:已打开扫码页时再次触发扫码,直接复用而非新建

        ​singleTask 模式采用任务栈重构策略。系统会优先搜索与目标 Activity 的 taskAffinity 匹配的任务栈,检测是否存在已有实例。如果找到,AMS 会先清除该实例之上的所有 Activity(触发它们的 onDestroy),将目标实例置于栈顶并调用 onNewIntent();若未找到,则新建独立任务栈。这种清除机制有效保障了栈内实例唯一性,但处理成本较高。

适用场景​:

  • 应用主页​(如微信主界面、设置首页)
  • 登录/授权中转站​(确保返回时跳过登录页)
    典型案例​:
  • 支付流程结束:商品页 → 收银台 → 支付成功页,点击“返回首页”清空整个支付栈,直达主页
  • 社交应用登录:若已登录过,再次打开APP直接进入主页面而非登录页

        ​singleInstance 模式具有全局隔离特性。该模式强制目标 Activity 独占整个任务栈,且栈内不允许其他 Activity 存在。启动时,AMS 先全局搜索专属栈:若存在则直接提至前台复用;若不存在则创建带唯一 ID 的新栈。这种模式实现了跨应用级的隔离,在系统低内存回收时可能被独立保留,常用于相机、拨号等系统组件。

适用场景​:

  • 系统级共享组件​(相机、通讯录选择器)
  • 独立功能模块​(如浮窗通话界面)
    典型案例​:
  • 系统相机调用:应用中启动相机拍照,返回时直接回到原应用
  • 第三方分享面板:微信分享时调起系统分享栈,完成后自动销毁
    特殊注意​:
  • 银行应用安全键盘:通过独立进程防止密码输入被截屏
场景需求推荐模式
常规页面跳转(允许多实例)standard
避免栈顶重复(如通知点击)singleTop
应用入口/核心页(需清栈)singleTask
独立系统组件(跨进程调用)singleInstance

        通过 AndroidManifest.xml 或 Intent 标志(如 FLAG_ACTIVITY_NEW_TASK)指定,控制 Activity 实例与任务栈(Task)的关系。

        onNewIntent() 是 Activity 被复用时的数据刷新入口,用于处理同一个 Activity 实例被再次启动时的新意图(Intent),而无需重建页面。

核心作用​(3个关键点):

  1. 复用已有页面

    • 避免重复创建相同 Activity(节省内存)
    • 保持当前页面状态(如滚动位置、输入内容)
  2. 更新数据

    protected void onNewIntent(Intent intent) {
        setIntent(intent); // 必须更新!否则getIntent()拿旧数据
        refreshUI(intent); // 根据新数据刷新界面
    }
  3. 特定场景触发

    • singleTop​:当 Activity 位于栈顶时
    • singleTask/singleInstance​:当 Activity 已存在于栈中时

典型场景​:

  • 点击通知栏多次打开同一聊天页(直接刷新消息)
  • 从支付结果页返回后再次支付(复用页面更新订单)
  • 全局搜索框重复搜索(保留搜索历史记录)

记住一个原则​:
只要看到 singleTop/singleTask,就要考虑是否需要重写 onNewIntent() 处理数据刷新!


启动模式在后台运行时的关键行为解析

一、后台启动 Activity 的通用规则
  1. 基本行为​:

    • 当应用在后台时启动新 Activity,系统会先将应用带回前台
    • 新 Activity 会被添加到当前任务栈中
    • 用户按返回键时会回到上一个 Activity
  2. 核心影响​:

    • 应用进程优先级提升到前台进程
    • 可能触发系统回收机制(低内存时后台进程优先被杀)

二、后台启动的注意事项

1. Android 8.0+ 限制
  • 后台启动限制​:应用在后台时无法随意启动 Activity
  • 解决方案​:
    // 必须使用全屏通知
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentIntent(pendingIntent) // 用户点击才启动
        .setFullScreenIntent(pendingIntent, true); // 紧急通知
2. 内存回收策略
启动模式回收优先级恢复难度
standard难(多实例)
singleTop中等
singleTask易(单实例)
singleInstance最低最易
3. 最佳实践
  1. 后台启动 singleTask 主页​:

    // 清理所有历史栈
    Intent intent = new Intent(context, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                   Intent.FLAG_ACTIVITY_CLEAR_TASK);
  2. 避免后台 standard 启动​:

    • 易导致 OOM(内存溢出)
    • 返回栈混乱
  3. 跨进程通信​:

    // 启动独立进程的Activity
    <activity android:process=":camera_process"/>

三、各模式后台表现对比表

启动模式后台启动特点内存效率适用场景
standard持续堆叠新实例需多实例的普通页面
singleTop栈顶复用省资源通知/消息更新
singleTask清理栈内多余页面应用主页/核心入口
singleInstance独立进程不受主应用影响最高相机/电话等系统级功能
模式实例数量栈位置典型场景
standard多个当前栈普通页面
singleTop栈顶唯一当前栈防重复启动(通知栏)
singleTask栈内唯一可指定新栈应用主界面
singleInstance全局唯一独占新栈独立功能(如相机)

微信小游戏双图标背后的启动模式解析

场景重现:

  1. 点击微信图标启动微信主界面

  2. 在微信内点击进入小游戏

  3. 后台出现两个独立图标​:

    • 微信主应用图标

    • 小游戏独立图标

核心启动模式:singleInstance

实现原理​:

<!-- 小游戏Activity声明示例 -->
<activity
    android:name=".GameActivity"
    android:launchMode="singleInstance"
    android:taskAffinity="com.tencent.game"
    android:process=":game_process" />

三大关键机制​:

  1. 独立进程

    android:process=":game_process"
    • 小游戏运行在独立的 com.tencent.mm:game_process 进程

    • 与微信主进程 com.tencent.mm 完全隔离

    • 效果​:系统显示两个独立进程图标

  2. 独立任务栈

    android:taskAffinity="com.tencent.game"
    android:launchMode="singleInstance"
    • 创建专属任务栈(与微信主栈隔离)

    • 效果​:

      • 最近任务显示两个独立任务项

      • 游戏退出时直接回到手机桌面,不经过微信

  3. 跨进程通信

    // 微信启动游戏的关键代码
    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.tencent.mm", "com.tencent.mm.plugin.game.GameActivity"));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    startActivity(intent);
    • 使用 FLAG_ACTIVITY_MULTIPLE_TASK 允许多实例

完整启动流程:

  1. 用户点击小游戏入口

  2. 微信创建子进程​:

    • 通过 startActivity() 跨进程启动

    • 指定 singleInstance + NEW_TASK 标志

  3. 系统创建资源隔离区​:

    • 独立内存空间(分配专属内存)

    • 独立渲染线程(避免微信主线程卡顿)

    • 独立任务栈(系统记录为独立应用)

  4. 游戏结束后​:

    • 游戏进程销毁

    • 微信主进程不受影响

    • 返回路径​:游戏 → 桌面 (不返回微信)

技术优势:

  1. 性能隔离​:

    • 游戏占500MB内存不影响微信聊天

    • 游戏崩溃不会导致微信闪退

    # 进程内存占用示例
    com.tencent.mm: 300MB   # 微信主进程
    com.tencent.mm:game: 500MB # 游戏进程
  2. 独立生命周期​:

    操作

    微信主进程

    游戏进程

    进入游戏

    onPause()

    onCreate()

    游戏切后台

    -

    onPause->onStop

    关闭游戏

    onResume()

    onDestroy()

    游戏崩溃

    无影响

    自动重启

  3. 用户体验优化​:

    • 游戏可独立操作(微信后台保持运行)

    • 小窗口模式双向互动(微信浮窗+游戏)

对比其他启动模式:

启动模式

是否分进程

是否独立图标

适用场景

standard

普通页面跳转

singleTask

微信钱包

singleTop

公众号文章

singleInstance

​**✅**​

小游戏/视频通话

典型应用场景:

  1. 微信/QQ内置小游戏

  2. 直播平台连麦功能

  3. 银行App的安全键盘

  4. AR扫描模块

ContentProvider应用场景

        ​ContentProvider 是 Android 实现应用间安全数据共享(基于 URI 和 Binder)的核心组件,同时通过空实现 Provider 的 onCreate() 机制实现库的自动初始化。​

ContentProvider 的主要应用场景

a) 应用间共享数据(最常见目的)​
*   **系统功能:** Android系统自身提供了大量ContentProvider让应用访问系统数据:
    *   `ContactsContract.Contacts` (通讯录联系人)
    *   `MediaStore.Images.Media` (相册图片)
    *   `MediaStore.Audio.Media` (音频文件)
    *   `CalendarContract.Events` (日历事件)
    *   `CallLog.Calls` (通话记录)
    *   `Settings.System` (系统设置)
*   **第三方应用:** 当你的应用需要向其他应用**提供数据**时(如提供用户配置、自定义数据库内容),你需要实现自己的ContentProvider,并定义相应的authority和URI结构。
 ​b) 同一个应用内的结构化数据访问
*   即使只在应用内部使用,ContentProvider也提供了一种**标准化**的方式来访问应用本身的数据库或结构化文件。这有利于统一数据访问逻辑、解耦UI和数据层(符合MVC/MVP/MVVM架构思想)、并便于以后扩展成跨应用共享(仅需添加权限和可能的URI结构调整)。
 ​c) 跨进程回调/协调
*   一些复杂的跨应用协作可以通过ContentProvider的`query()`、`insert()`等操作来触发。例如,应用A通过调用应用B的ContentProvider插入一条特定数据,可以作为一种信号通知应用B执行某项操作(不过Intent广播和Service是更直接的IPC方式)。
 ​d) 应用初始化 - Auto-initialization Providers
*   **这是非常关键且容易被问到的点!** 你在开发中可能会遇到一些第三方库(或Android Jetpack库),需要在应用启动(`Application.onCreate()`之前)就立即执行初始化。**ContentProvider 是实现这种“自动初始化”的核心机制。**
*   **原理:** 系统会在应用进程启动时,**早于**创建 `Application` 实例并调用其 `onCreate()` 方法之前,先**实例化**并**初始化**(调用 `onCreate()` 方法)清单文件中所有在`<application>`标签里声明的ContentProvider。
*   **库的做法:** 一个需要预初始化的库可以在其 SDK 中包含一个**不暴露任何实际数据 URI 的“空” ContentProvider**。
    1.  这个 Provider 在清单文件中声明。
    2.  它的唯一目的是在其 `onCreate()` 方法中执行库所需的初始化代码(如初始化单例、设置全局配置、数据库设置等)。
    3.  由于其 `onCreate()` 调用时机非常早(在 `Application.onCreate()` 之前),库就能确保在应用的任何代码运行前完成初始化。
*   **常见例子:**
    *   **WorkManager (>= v2.1):** 使用 `WorkManagerInitializer` Provider 来自动初始化。
    *   **Firebase:** `FirebaseApp`的初始化通常通过Provider(如`FirebaseInitProvider`)完成。
    *   **Google Play Services SDKs:** 某些服务可能使用此机制。
    *   **Mockito / Robolectric 等测试框架:** 有时用于注入测试依赖。
*   **优势:** 开发者只需引入库依赖并配置清单文件(通常是自动合并的),无需在 `Application.onCreate()` 中显式调用 `initialize()` 方法,减少了初始化配置遗漏的可能性。
*   **注意:** 过度使用会略微增加启动时间,因为系统需要加载和初始化这些 Provider。应仅用于必要的库初始化。

代码示例: 

利用 ContentProvider 实现应用初始化

 自定义初始化 Provider 实现 (空内容提供者)
public class AppInitProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        // 系统在 Application.onCreate() 前调用
        initializeApp();
        return true; // 初始化成功
    }

    private void initializeApp() {
        // 1. 初始化单例配置
        AppConfig.initialize(getContext());
        
        // 2. 设置全局异常处理
        Thread.setDefaultUncaughtExceptionHandler(new AppCrashHandler());
        
        // 3. 初始化网络请求库
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .build();
        RetrofitManager.init(client);
        
        // 4. 初始化数据库框架
        Room.databaseBuilder(
                getContext(), 
                AppDatabase.class, 
                "app-db"
        ).fallbackToDestructiveMigration().build();
        
        // 5. 三方库初始化 (示例)
        FirebaseApp.initializeApp(getContext());
    }

    // 空方法 - 此 Provider 不提供实际数据
    @Nullable @Override public Cursor query(...) { return null; }
    @Nullable @Override public Uri insert(...) { return null; }
    @Override public int delete(...) { return 0; }
    @Override public int update(...) { return 0; }
    @Nullable @Override public String getType(Uri uri) { return null; }
}
 AndroidManifest.xml 配置
<application>
    <!-- 系统将优先初始化此 Provider -->
    <provider
        android:name=".init.AppInitProvider"
        android:authorities="${applicationId}.appinit"  <!-- 唯一标识符 -->
        android:exported="false"  <!-- 不对外暴露 -->
        android:initOrder="100"   <!-- 多个 Provider 时指定初始化顺序 -->
        tools:ignore="ExportedContentProvider"/> 
    
    <!-- 常规 Application 类 -->
    <activity
        android:name=".MainActivity"
        .../>
</application>
 Application 类中的后续初始化
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 此时 AppInitProvider 已完成初始化
        
        // 1. 初始化必须依赖前序操作的组件
        initializePushNotifications();
        
        // 2. 验证初始化状态
        if(!AppConfig.isInitialized()) {
            throw new RuntimeException("初始化失败! 请检查AppInitProvider");
        }
    }
    
    private void initializePushNotifications() {
        // 依赖 AppInitProvider 中初始化的网络库
        PushService.setHttpClient(RetrofitManager.getClient());
    }
}

使用此模式后,开发者只需添加依赖库,​零代码初始化三方库​ - 特别适合SDK开发者。如 Firebase 通过此机制实现完全自动初始化,无需在 Application 中调用 FirebaseApp.initializeApp()。 

Service 启动方式与实战场景详解

        Service是Android执行后台长时操作(如音乐播放、网络下载)或跨进程通信(通过AIDL)的核心组件,其生命周期由系统管理,分为启动状态(startService())和绑定状态(bindService()),8.0+系统需结合前台服务或JobScheduler规避后台限制。​

一、两种核心启动方式
1. startService() 启动方式

本质特点​:

  • 启动后服务与组件完全解耦
  • 生命周期独立运行
  • 必须显式停止​(调用 stopSelf() 或 stopService())

完整生命周期流程​:

  1. 首次启动:onCreate() → onStartCommand()
  2. 后续启动:直接触发 onStartCommand()
  3. 停止服务:onDestroy()

场景适用​:

  • 后台长期任务​:如音乐播放、定位追踪
  • 无交互任务​:如日志上传、数据同步
  • 跨应用操作​:如推送消息处理

实战代码​:

// 启动下载服务
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.putExtra("file_url", "https://example.com/file.zip");
startService(downloadIntent);

// 服务内部停止
public class DownloadService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(() -> {
            downloadFile(intent.getStringExtra("file_url"));
            stopSelf();  // 下载完成后自动停止
        }).start();
        return START_NOT_STICKY;
    }
}
2. bindService() 启动方式

本质特点​:

  • 建立组件与服务的绑定关系
  • 通过 IBinder 接口实现双向通信
  • 绑定解绑自动管理生命周期

完整生命周期流程​:

  1. 首次绑定:onCreate() → onBind()
  2. 通信期间:通过 Binder 接口交互
  3. 所有绑定解除:onUnbind() → onDestroy()

场景适用​:

  • 实时交互场景​:如音乐控制(播放/暂停/进度)
  • 功能模块解耦​:如支付模块服务
  • 跨进程通信(IPC)​​:不同应用间的数据交换

实战代码​:

// 绑定音乐服务
ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        MusicService.MusicBinder musicBinder = (MusicService.MusicBinder) binder;
        musicBinder.play();  // 直接调用服务方法
    }
};
bindService(new Intent(this, MusicService.class), conn, BIND_AUTO_CREATE);

// 解绑服务
unbindService(conn);

Service使用场景

音乐/视频播放服务

核心实现方案​:

public class MediaPlaybackService extends Service 
    implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {
    
    private MediaPlayer mediaPlayer;
    private MediaSession mediaSession;
    private static final int NOTIFICATION_ID = 101;

    @Override
    public void onCreate() {
        // 初始化媒体播放器
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build());
        
        // 创建媒体会话(支持锁屏控制)
        mediaSession = new MediaSession(this, "MediaPlaybackService");
        mediaSession.setCallback(new MediaSessionCallback());
        mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | 
                             MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
        
        // 设置通知通道(Android 8.0+必须)
        createNotificationChannel();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getStringExtra("action");
        
        if("PLAY".equals(action)) {
            String mediaUrl = intent.getStringExtra("media_url");
            playMedia(mediaUrl);
        } 
        else if("PAUSE".equals(action)) {
            pausePlayback();
        }
        
        return START_NOT_STICKY;
    }

    private void playMedia(String mediaUrl) {
        try {
            // 停止当前播放
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            }
            
            mediaPlayer.reset();
            mediaPlayer.setDataSource(mediaUrl);
            mediaPlayer.prepareAsync(); // 异步准备播放
            mediaPlayer.setOnPreparedListener(this);
            
            // 转换为前台服务
            Notification notification = buildMediaNotification("正在播放");
            startForeground(NOTIFICATION_ID, notification);
            
        } catch (IOException e) {
            Log.e("MediaService", "播放初始化失败", e);
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        mediaPlayer.start();
        mediaSession.setActive(true);
        
        // 更新通知显示播放状态
        Notification notification = buildMediaNotification("正在播放");
        NotificationManager nm = getSystemService(NotificationManager.class);
        nm.notify(NOTIFICATION_ID, notification);
    }

    // 构建媒体通知(含播放控制按钮)
    private Notification buildMediaNotification(String status) {
        // 构建通知内容(包含播放控制按钮)
        // ...
    }
}

关键技术与注意事项​:

  1. 音频焦点管理​:
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(
    focusChangeListener, 
    AudioManager.STREAM_MUSIC, 
    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
  1. 耳机事件处理​:
// 注册耳机拔出广播
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(noisyReceiver, intentFilter);

BroadcastReceiver noisyReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if(AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
            pausePlayback(); // 耳机拔出时暂停播放
        }
    }
};
  1. 通知栏控制​:
  • 通过 MediaStyle 通知样式添加播放控制按钮
  • 更新播放进度(Android 12+支持直接显示播放进度条)
  1. 播放状态持久化​:
  • 在 onDestroy() 中保存当前播放位置
  • 在 onCreate() 中恢复最后播放位置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2407378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…