曾经被我们忽略的AVAudioSession

本文深入解析AVAudioSession的Category和AVAudioSessionCategoryOptions,展示如何通过不同配置实现音频播放、录制及与背景音频的交互模式,包括中断、混音、降低背景音等功能。

AVAudioSession是用来管理和平衡多个App的(扬声器、麦克疯)的资源的使用。
例如设备在背后进行播放音乐时,这时候用户进入我们的App,需要播放一小段视频时,这个时候,我们应该如何处理?

  • 中断音乐播放?
  • 暂时中断音乐播放,等小视频播放完毕唤醒背后音乐继续播放?
  • 与音乐混音一起播放?
  • 与音乐混音播放暂时压低背后音乐的声音,等小视频播放完毕恢复背后音乐的声音大小?
    这些处理方式都是依靠AVAudioSession来处理的。

1.AVAudioSession 的 Category

我们进入 AVAudioSession 的Category可以发现有7种 Category 可供选择。

AVF_EXPORT NSString *const AVAudioSessionCategoryAmbient;

/*  Use this category for background sounds.  Other music will stop playing. */
AVF_EXPORT NSString *const AVAudioSessionCategorySoloAmbient;

/* Use this category for music tracks.*/
AVF_EXPORT NSString *const AVAudioSessionCategoryPlayback;

/*  Use this category when recording audio. */
AVF_EXPORT NSString *const AVAudioSessionCategoryRecord;

/*  Use this category when recording and playing back audio. */
AVF_EXPORT NSString *const AVAudioSessionCategoryPlayAndRecord;

/*  Use this category when using a hardware codec or signal processor while
 not playing or recording audio. */
AVF_EXPORT NSString *const AVAudioSessionCategoryAudioProcessing NS_DEPRECATED_IOS(3_0, 10_0) __TVOS_PROHIBITED __WATCHOS_PROHIBITED;

/*  Use this category to customize the usage of available audio accessories and built-in audio hardware.
 For example, this category provides an application with the ability to use an available USB output 
 and headphone output simultaneously for separate, distinct streams of audio data. Use of 
 this category by an application requires a more detailed knowledge of, and interaction with, 
 the capabilities of the available audio routes.  May be used for input, output, or both.
 Note that not all output types and output combinations are eligible for multi-route.  Input is limited
 to the last-in input port. Eligible inputs consist of the following:
    AVAudioSessionPortUSBAudio, AVAudioSessionPortHeadsetMic, and AVAudioSessionPortBuiltInMic.  
 Eligible outputs consist of the following: 
    AVAudioSessionPortUSBAudio, AVAudioSessionPortLineOut, AVAudioSessionPortHeadphones, AVAudioSessionPortHDMI, 
    and AVAudioSessionPortBuiltInSpeaker.  
 Note that AVAudioSessionPortBuiltInSpeaker is only allowed to be used when there are no other eligible 
 outputs connected.  */
AVF_EXPORT NSString *const AVAudioSessionCategoryMultiRoute
  • AVAudioSessionCategoryAmbient 仅支持播放,不会打断不支持混音的App,使用这种模式,你的App的声音会与背后的音乐App一起发出声音。 如果是在锁屏或者静音键的情况下你App的音频会终止。
  • AVAudioSessionCategorySoloAmbient 仅支持播放,会打断不支持混音的App,使用这种模式,你的App的声音会打断背后的音乐App。 如果是在锁屏或者静音键的情况下你App的音频会终止。 这种模式是系统的默认模式。
  • AVAudioSessionCategoryPlayback 仅支持播放,默认会打断不支持混音的App。如果是在锁屏或者静音键的情况下你App的音频不会终止。
  • AVAudioSessionCategoryRecord 仅支持录制,会打断不支持混音的App。如果是在锁屏或者静音键的情况下仍可录制。
  • AVAudioSessionCategoryPlayAndRecord 支持播放且支持录制,默认会打断不支持混音的App。如果是在锁屏或者静音键的情况下仍可播放声音或者录制。
  • AVAudioSessionCategoryMultiRoute 支持播放且支持录制,会打断不支持混音的App。如果是在锁屏或者静音键的情况下仍可播放声音或者录制。
  • AVAudioSessionCategoryAudioProcessing 不支持播放且不支持录音,会打断不支持混音的App。iOS10 之后被弃用。

2.AVAudioSession 的 AVAudioSessionCategoryOptions

AVAudioSession 的AVAudioSessionCategoryOptions 同样也是一个枚举。

    /* MixWithOthers is only valid with AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and  AVAudioSessionCategoryMultiRoute */
    AVAudioSessionCategoryOptionMixWithOthers           = 0x1,

    /* DuckOthers is only valid with AVAudioSessionCategoryAmbient, AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute */
    AVAudioSessionCategoryOptionDuckOthers              = 0x2,

    /* AllowBluetooth is only valid with AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord */
    AVAudioSessionCategoryOptionAllowBluetooth  __TVOS_PROHIBITED __WATCHOS_PROHIBITED      = 0x4,

    /* DefaultToSpeaker is only valid with AVAudioSessionCategoryPlayAndRecord */
    AVAudioSessionCategoryOptionDefaultToSpeaker __TVOS_PROHIBITED __WATCHOS_PROHIBITED     = 0x8,

    /* InterruptSpokenAudioAndMixWithOthers is only valid with AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute */
    AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers NS_AVAILABLE_IOS(9_0) = 0x11,

    /* AllowBluetoothA2DP is only valid with AVAudioSessionCategoryPlayAndRecord */
    AVAudioSessionCategoryOptionAllowBluetoothA2DP API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0)) = 0x20,

    /* AllowAirPlay is only valid with AVAudioSessionCategoryPlayAndRecord */
    AVAudioSessionCategoryOptionAllowAirPlay API_AVAILABLE(ios(10.0), tvos(10.0)) __WATCHOS_PROHIBITED = 0x40,
  • AVAudioSessionCategoryOptionMixWithOthers 当你的App包含声音播放时,设置这个选项在激活会话时不会打断其他App的音频播放。
    适用于以下category:
    • AVAudioSessionCategoryPlayback
    • AVAudioSessionCategoryPlayAndRecord
    • AVAudioSessionCategoryMultiRoute
  • AVAudioSessionCategoryOptionDuckOthers 当你的App包含声音播放时,设置这个选项在激活会话时会降低其他App的声音播放。
    适用于以下category:
    • AVAudioSessionCategoryAmbient
    • AVAudioSessionCategoryPlayAndRecord
    • AVAudioSessionCategoryPlayback
    • AVAudioSessionCategoryMultiRoute
  • AVAudioSessionCategoryOptionAllowBluetooth 允许可免提蓝牙设备可使用输入通道
    适用于以下category:
    • AVAudioSessionCategoryRecord
    • AVAudioSessionCategoryPlayAndRecord
  • AVAudioSessionCategoryOptionDefaultToSpeaker 在没有其他通道的时候默认选择内置扬声器
    适用于以下category:
    • AVAudioSessionCategoryPlayAndRecord
  • AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers 你的App偶尔的使用音频播放。
    适用于以下category:
    • AVAudioSessionCategoryPlayback
    • AVAudioSessionCategoryPlayAndRecord
    • AVAudioSessionCategoryMultiRoute
  • AVAudioSessionCategoryOptionAllowBluetoothA2DP 立体声蓝牙。
    适用于以下category:
    • AVAudioSessionCategoryPlayAndRecord
  • AVAudioSessionCategoryOptionAllowAirPlay 远程AirPlay设备。
    适用于以下category:
    • AVAudioSessionCategoryPlayAndRecord

3. 使用AVAudioSession 的 Category 和 AVAudioSessionCategoryOptions 调整各种音频模式

1.播放音视频的时候直接中断背后的音乐播放。

这种情况下,我们直接将category 设置为 AVAudioSessionCategorySoloAmbient即可,当然你不做任何处理系统也会进行默认模式处理。很多App也是这种模式。

    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError * error = nil;
    [session setCategory:AVAudioSessionCategorySoloAmbient error:&error];
    if (error) {
        NSLog(@"%@",error);
    }

2.播放音视频的时候暂时中断背后的音乐,播放完毕后再继续背后的音乐。

这种情况下,category设置与1的相同,然后在播放前 增加下面的代码告诉背后的音乐App你将要占用音频焦点。

[[AVAudioSession sharedInstance] setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];

在暂停播放或者App退到后台后暂停播放前,告诉背后的音乐App你取消音频焦点的占用。

[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];

3.播放音视频的时候与背后音乐App混音一起播放

这种情况下,我们直接将category 设置为 AVAudioSessionCategoryAmbient即可。

    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError * error = nil;
    [session setCategory:AVAudioSessionCategoryAmbient error:&error];
    if (error) {
        NSLog(@"%@",error);
    }

4.播放音视频的时候暂时压低背后音乐的声音,等音视频播放完毕恢复背后音乐的声音大小

这种情况下,需要设置category为AVAudioSessionCategoryAmbient,AVAudioSessionCategoryOptions设置为AVAudioSessionCategoryOptionDuckOthers

 AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError * error = nil;
    [session setCategory:AVAudioSessionCategoryAmbient withOptions:AVAudioSessionCategoryOptionDuckOthers error:&error];
    if (error) {
        NSLog(@"%@",error);
    }

然后在播放前 增加下面的代码告诉背后的音乐App你将要占用音频焦点。

[[AVAudioSession sharedInstance] setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];

在暂停播放或者App退到后台后暂停播放前,告诉背后的音乐App你取消音频焦点的占用。

[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];

以上是一些AVAudioSession 的 Category 和 AVAudioSessionCategoryOptions 配合使用的几种模式的举例,具体要根据自己的实际需要进行调整。Demo下载地址

这段代码 ios app上面运行后 没有打印 是因为弹出来一次之后 就没发再弹出来的原因吗 怎么处理这个方法 // iOS 请求权限 requestIOSPermission() { return new Promise((resolve) => { // iOS 直接使用系统API请求授权 uni.authorize({ scope: this.getIOSScope(), success: () => { console.log(111); this.$emit('changeAuth'); resolve(true); }, fail: () => { console.log(222); this.showSettingGuide(); resolve(false); } }); }); }, // 获取 iOS 对应的 scope getIOSScope() { const mapping = { 'ACCESS_FINE_LOCATION': 'scope.userLocation', 'WRITE_EXTERNAL_STORAGE': 'scope.writePhotosAlbum', 'CAMERA': 'scope.camera', 'RECORD_AUDIO': 'scope.record', 'READ_PHONE_STATE': 'scope.notification', 'CALL_PHONE': 'scope.addressBook' }; return mapping[this.permissionID] || ''; }, 然后这是全部代码 情节和我的全部逻辑 优化完整: <template> <!-- 权限申请弹窗组件 --> <view v-if="showPopup" class="uni-popup" :style="{top:isNativeHead?'':StatusBar}"> <view :class="[type, ani, animation ? 'ani' : '']" class="uni-custom uni-popup__wrapper" @click.stop="close(true)"> <view class="uni-popup__wrapper-box"> <view class="title">{{currentPermissionData.title}}</view> <view class="content">{{currentPermissionData.content}}</view> <!-- 操作按钮区域 --> <view class="action-buttons"> <button class="btn cancel" @click.stop="handleLater">稍后再说</button> <button class="btn confirm" @click.stop="handleAuthorize">立即授权</button> </view> </view> </view> </view> </template> <script> // 权限信息配置 - 集中管理 const PERMISSION_DATA = { // 'WRITE_EXTERNAL_STORAGE': { // title: "霍小帮对存储空间/照片权限申请说明", // content: "便于您使用该功能上传您的照片/图片/视频及用于更换头像、意见反馈、保存相册、发布商品/分享、下载与客服沟通等场景中读取和写入相册和文件内容。" // }, // 'ACCESS_FINE_LOCATION': { // title: "霍小帮对地理位置权限申请说明", // content: "便于应用程序可以提供基于位置的服务、定位导航、附近搜索等功能。APP收集WIFI扫描结果的目的为提供定位服务,仅在首页获取定位的城市的时候需要使用到,只收集周围可用WIFI网络的名称和信号强度等信息,不收集WIFI密码等敏感信息!为同城配送功能会获取骑手当前位置,不储存任何隐私信息!" }, // 'CAMERA':{ // title: "霍小帮对相机/摄像头权限申请说明", // content: "便于您使用该功能拍照上传您的照片/视频及用于更换头像、意见反馈、保存相册、发布商品/动态、下载与客服沟通等场景中使用" // }, // 'RECORD_AUDIO':{ // title: "霍小帮对麦克风权限申请说明", // content: "便于您使用该功能进行录音、语音通话、发布语音、与客服语音沟通等场景中使用" // }, // 'READ_PHONE_STATE':{ // title: "霍小帮对获取设备信息申请说明", // content: "首次或切换设备请允许此权限,便于您收到APP的系统通知或APP内平台公告/好友消息通知的场景中使用,不会泄露任何个人信息,我们只保存您的设备ID用于发送消息通知" // }, // 'CALL_PHONE': { // title: "霍小帮对拨打/管理电话权限申请说明", // content: "便于您使用该功能联系买家、骑手或者客服、业务经理与联系等场景下使用" // } WRITE_EXTERNAL_STORAGE: { title: "存储空间/照片权限说明", content: "用于上传照片/图片/视频、更换头像、意见反馈、保存相册等功能", iosPermission: "PHPhotoLibrary" // iOS 对应的权限类 }, ACCESS_FINE_LOCATION: { title: "地理位置权限说明", content: "用于提供定位服务、附近搜索等功能,保护您的隐私安全", iosPermission: "CLLocationManager" }, CAMERA: { title: "相机/摄像头权限说明", content: "用于拍照上传照片/视频、更换头像、发布商品等功能", iosPermission: "AVCaptureDevice" }, RECORD_AUDIO: { title: "麦克风权限说明", content: "用于语音通话、语音消息、客服沟通等功能", iosPermission: "AVAudioSession" }, READ_PHONE_STATE: { title: "设备信息权限说明", content: "用于系统通知、消息推送功能,保护您的个人信息安全", iosPermission: "CNContactStore" }, CALL_PHONE: { title: "电话权限说明", content: "用于联系买家、骑手或客服等功能", iosPermission: "CNContactStore" } }; export default { name: 'YkAuthPopup', props: { animation: { type: Boolean, default: true }, type: { type: String, default: 'top' }, show: { type: Boolean, default: true }, isNativeHead: { type: Boolean, default: false }, permissionID: { type: [String, Number], required: true } }, data() { return { ani: '', showPopup: false, StatusBar: '', refuseNum: 0, // 拒绝次数 currentPermissionData: {}, // 当前权限数据 authList: PERMISSION_DATA // 权限配置映射 } }, watch: { permissionID: { immediate: true, handler(newVal) { // 更新当前权限数据 this.currentPermissionData = this.authList[newVal] || {}; } } }, created() { // #ifdef APP-PLUS this.getSystemInfo(); // #endif }, methods: { // 获取状态栏高度 getSystemInfo() { uni.getSystemInfo({ success: (e) => { this.StatusBar = e.statusBarHeight + 'px'; } }) }, open() { this.showPopup = true; this.$nextTick(() => { setTimeout(() => { this.ani = 'uni-' + this.type; // 延迟后自动检查权限 setTimeout(() => { this.checkPermissions(); }, 300); }, 30); }); }, close() { this.ani = ''; this.$nextTick(() => { setTimeout(() => { this.showPopup = false; }, 300); }); }, // 稍后再说处理 handleLater() { this.close(); this.refuseNum++; // 如果拒绝超过3次,显示引导提示 if (this.refuseNum >= 3) { this.$emit('permission-denied', this.permissionID); } }, // 立即授权处理 handleAuthorize() { this.close(); this.requestPermissions(); }, // 统一权限检查方法 async checkPermissions() { var _this = this const platform = plus.os.name; // Android 权限检查 if (platform === 'Android') { try { const permissionStr = 'android.permission.' + this.permissionID; const result = await new Promise((resolve) => { plus.android.checkPermission(permissionStr, resolve); }); if (result?.checkResult === -1) { // 未授权:显示弹窗(已显示) return; } // 已授权,通知父组件 this.$emit('changeAuth'); } catch (error) { console.error('权限检查失败:', error); } } // iOS 权限检查 else if (platform === 'iOS') { // try { // const granted = await this.checkIOSPermission(); // if (granted) { // // 已授权,通知父组件 // this.$emit('changeAuth'); // } // } catch (error) { // console.error('iOS权限检查失败:', error); // } //IOS不需要添加自定义弹框来描述权限目的,因为在配置文件的隐私信息访问的许可描述里可添加 //正常可以直接调用uni的API调起权限询问弹框使用各种权限,下面的判断使用场景主要是在IOS禁用某权限后,这个可以判断有无权限,进而引导用户跳转设置开启,仅列出了位置、相册、通讯录、相机、录音等权限,其他IOS权限可具体参考 https://ext.dcloud.net.cn/plugin?id=15787 let result = 0; console.log(permissionID,'permissionID'); if (permissionID == 'ACCESS_FINE_LOCATION') { //IOS检测位置权限 let cLLocationManager = plus.ios.importClass("CLLocationManager"), authStatus = cLLocationManager.authorizationStatus(), enable = cLLocationManager.locationServicesEnabled(); if (enable && authStatus != 2) { result = 1; } else { result = 0; } plus.ios.deleteObject(cLLocationManager); } else if (permissionID == 'WRITE_EXTERNAL_STORAGE') { //IOS检测相册权限 let PHPhotoLibrary = plus.ios.importClass("PHPhotoLibrary"), authStatus = PHPhotoLibrary.authorizationStatus(); if (authStatus === 3) { result = 1; } else { result = 0; } plus.ios.deleteObject(PHPhotoLibrary); } else if (permissionID == 'CAMERA') { //IOS检测相机/摄像头权限 let avCaptureDevice = plus.ios.importClass("AVCaptureDevice"), authStatus = avCaptureDevice.authorizationStatusForMediaType("vide"); if (authStatus === 3) { result = 1; } else { result = 0; } plus.ios.deleteObject(avCaptureDevice); } else if (permissionID == 'CALL_PHONE') { //IOS检测通讯录权限 let contactStore = plus.ios.importClass("CNContactStore"), authStatus = contactStore.authorizationStatusForEntityType(0); // console.log(authStatus,'authStatus'); if (authStatus === 3) { result = 1; } else { result = 0; } plus.ios.deleteObject(contactStore); }else if(permissionID == 'RECORD_AUDIO'){ //IOS检测麦克风权限 let aVAudioSession = plus.ios.importClass("AVAudioSession"), aVAudio = aVAudioSession.sharedInstance(), authStatus = aVAudio.recordPermission(); if ([1684369017, 1970168948].includes(authStatus)) { result = 0; } else { result = 1; } plus.ios.deleteObject(aVAudioSession); } if (result) { //当前查询权限已授权,此时可以通知页面执行接下来的操作 _this.$emit('changeAuth') } else { //当前查询的权限已禁用,引导用户跳转手机系统设置去开启 if(permissionID == 'READ_PHONE_STATE'){ }else{ console.log(this.currentPermissionData,'this.currentPermissionData'); this.$util.showModal({ title: '温馨提示【'+permissionID+'】', content: '请再设置打开此权限,更好使用此页面功能~',//是否立刻前往?' // cancelText: "取消", confirmText: "我知道了", showCancel: true, confirmColor: '#000', cancelColor: '#666', success: (res) => { if (res.confirm) { // _this.goSetting(); } } }) } } } }, // 修复后的 iOS 权限检查方法 async checkIOSPermission() { try { // 特殊处理不需要权限检查的情况 if (this.permissionID === 'READ_PHONE_STATE') { // iOS 设备信息不需要额外权限 return true; } // 获取对应的权限类名 const permissionClass = this.currentPermissionData?.iosPermission; if (!permissionClass) { console.error(`未配置权限类: ${this.permissionID}`); return false; } // 安全导入 iOS 类 let manager; try { manager = plus.ios.importClass(permissionClass); if (!manager) { console.warn(`无法导入iOS类: ${permissionClass}`); return false; } } catch (importError) { console.error(`导入iOS类失败: ${permissionClass}`, importError); return false; } // 统一权限状态检查 let granted = false; switch (this.permissionID) { case 'ACCESS_FINE_LOCATION': // 位置权限检查 const authStatus = manager.authorizationStatus?.(); const enabled = manager.locationServicesEnabled?.(); granted = !!enabled && authStatus !== 2; break; case 'WRITE_EXTERNAL_STORAGE': // 存储权限检查 granted = manager.authorizationStatus?.() === 3; break; case 'CAMERA': // 相机权限检查 granted = manager.authorizationStatusForMediaType?.("vide") === 3; break; case 'CALL_PHONE': case 'READ_PHONE_STATE': // 电话权限检查 // iOS 14+ 需要使用 CNContactStore 检查通讯录权限 const authStatusPhone = manager.authorizationStatusForEntityType?.(0); granted = authStatusPhone === 3; break; case 'RECORD_AUDIO': // 麦克风权限检查 const session = manager.sharedInstance?.(); const status = session?.recordPermission?.(); // 使用更安全的状态检查方式 const deniedStatuses = [1684369017, 1970168948]; granted = !deniedStatuses.includes(status); break; default: console.warn(`未处理的权限类型: ${this.permissionID}`); granted = false; } return granted; } catch (error) { console.error(`iOS权限检查失败: ${this.permissionID}`, error); return false; } finally { // 确保释放原生对象 if (manager) { try { plus.ios.deleteObject(manager); } catch (deleteError) { console.warn(`释放iOS对象失败:`, deleteError); } } } }, // 请求权限的统一方法 async requestPermissions() { const platform = plus.os.name; if (platform === 'Android') { await this.requestAndroidPermission(); } else if (platform === 'iOS') { await this.requestIOSPermission(); } }, // Android 请求权限 requestAndroidPermission() { return new Promise((resolve) => { const permissionStr = 'android.permission.' + this.permissionID; plus.android.requestPermissions([permissionStr], (e) => { if (e.granted.length > 0) { this.$emit('changeAuth'); } else if (e.deniedAlways.length > 0) { this.showSettingGuide(); } resolve(); }); }); }, // iOS 请求权限 requestIOSPermission() { return new Promise((resolve) => { // iOS 直接使用系统API请求授权 uni.authorize({ scope: this.getIOSScope(), success: () => { console.log(111); this.$emit('changeAuth'); resolve(true); }, fail: () => { console.log(222); this.showSettingGuide(); resolve(false); } }); }); }, // 获取 iOS 对应的 scope getIOSScope() { const mapping = { 'ACCESS_FINE_LOCATION': 'scope.userLocation', 'WRITE_EXTERNAL_STORAGE': 'scope.writePhotosAlbum', 'CAMERA': 'scope.camera', 'RECORD_AUDIO': 'scope.record', 'READ_PHONE_STATE': 'scope.notification', 'CALL_PHONE': 'scope.addressBook' }; return mapping[this.permissionID] || ''; }, // 显示设置引导 showSettingGuide() { this.$util.showModal({ title: '温馨提示', content: '请前往系统设置开启权限,以获得完整功能体验', cancelText: "取消", confirmText: "前往设置", showCancel: true, confirmColor: '#000', cancelColor: '#666', success: (res) => { if (res.confirm) { this.goSetting(); } } }); }, // 跳转系统设置 goSetting() { // Android if (plus.os.name === "Android") { const Intent = plus.android.importClass("android.content.Intent"); const Settings = plus.android.importClass("android.provider.Settings"); const Uri = plus.android.importClass("android.net.Uri"); const mainActivity = plus.android.runtimeMainActivity(); const intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); const uri = Uri.fromParts("package", mainActivity.getPackageName(), null); intent.setData(uri); mainActivity.startActivity(intent); } // iOS else { const UIApplication = plus.ios.import("UIApplication"); const application = UIApplication.sharedApplication(); const NSURL = plus.ios.import("NSURL"); try { // 优先使用官方设置URL const settingsURL = NSURL.URLWithString(UIApplicationOpenSettingsURLString); if (application.canOpenURL(settingsURL)) { const iosVersion = parseFloat(plus.os.version); if (iosVersion >= 10.0) { application.openURLOptionsCompletionHandler(settingsURL, {}, null); } else { application.openURL(settingsURL); } return; } } catch (e) { console.warn("使用官方设置URL失败,尝试备选方案"); } // 备选方案 const settingsURL = NSURL.URLWithString("app-settings:"); application.openURL(settingsURL); } } } } </script> <style lang="scss"> .uni-popup { position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: 99999; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: flex-start; justify-content: center; &__wrapper { position: relative; margin-top: 100rpx; &.ani { transition: all 0.3s ease-out; } &.top { animation: slideDown 0.3s forwards; } &-box { width: 650rpx; background: #fff; border-radius: 20rpx; overflow: hidden; box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15); .title { padding: 30rpx 30rpx 10rpx; font-size: 34rpx; font-weight: bold; color: #333; text-align: center; } .content { padding: 20rpx 40rpx; font-size: 28rpx; color: #666; line-height: 1.6; text-align: center; } } } } .action-buttons { display: flex; border-top: 1rpx solid #eee; margin-top: 20rpx; .btn { flex: 1; height: 90rpx; line-height: 90rpx; font-size: 32rpx; border-radius: 0; background: none; position: relative; margin: 0; &::after { border: none; } &.cancel { color: #999; border-right: 1rpx solid #eee; } &.confirm { color: #007AFF; font-weight: 500; } } } @keyframes slideDown { from { transform: translateY(-100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } </style>
最新发布
11-03
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值