Android 音乐APP(五)音乐通知栏、后台播放音乐
Android 音乐通知栏
- 前言
- 正文
- ① 通知栏按钮点击监听
- ② 通知栏点击监听
- ③ 通知栏业务处理
- ④ 运行效果图
- 结语
前言
这篇文章的标题有些言简意赅了,也突出了这篇文章的核心,那就是通知栏的操作,你可以看到市面上的音乐类APP都会有这个操作,通过音乐通知栏可以播放暂停、上一曲、下一曲、收藏、显示歌词等等。当然我这个Demo目前不考虑这么多,先实现播放暂停、上一曲、下一曲这些基本功能再说,你说对吧。
正文
在第四篇文章的到最后显示了通知栏,那么为什么我要把通知的的操作单独放到一篇文章来进行讲解呢?因为里面有很多业务逻辑,还有通信的关系,所以才这么做的。
① 通知栏按钮点击监听
要实现具体的业务功能,首先要监听到点击事件,这一点是毋庸置疑的,谁赞成,谁反对。首先增加几个全局变量,打开Constant
/*** 歌曲播放*/public static final String PLAY = "play";/*** 歌曲暂停*/public static final String PAUSE = "pause";/*** 上一曲*/public static final String PREV = "prev";/*** 下一曲*/public static final String NEXT = "next";/*** 关闭通知栏*/public static final String CLOSE = "close";/*** 进度变化*/public static final String PROGRESS = "progress";
这些都是用来表明当前歌曲的状态的,至关重要。之前我通过RemoteViews来指定一个布局文件,从而实现自定义通知栏样式的效果,那么对于通知栏页面的按钮的点击事件,也是交给RemoteViews来完成来的,下面进行实例化,把它变成成员变量。
在Service中实例化
private static RemoteViews remoteViews;
然后单独写一个方法对RemoteViews进行初始化配置。
/*** 初始化自定义通知栏 的按钮点击事件*/private void initRemoteViews() {remoteViews = new RemoteViews(this.getPackageName(), R.layout.notification);//通知栏控制器上一首按钮广播操作Intent intentPrev = new Intent(PREV);PendingIntent prevPendingIntent = PendingIntent.getBroadcast(this, 0, intentPrev, 0);//为prev控件注册事件remoteViews.setOnClickPendingIntent(R.id.btn_notification_previous, prevPendingIntent);//通知栏控制器播放暂停按钮广播操作 //用于接收广播时过滤意图信息Intent intentPlay = new Intent(PLAY);PendingIntent playPendingIntent = PendingIntent.getBroadcast(this, 0, intentPlay, 0);//为play控件注册事件remoteViews.setOnClickPendingIntent(R.id.btn_notification_play, playPendingIntent);//通知栏控制器下一首按钮广播操作Intent intentNext = new Intent(NEXT);PendingIntent nextPendingIntent = PendingIntent.getBroadcast(this, 0, intentNext, 0);//为next控件注册事件remoteViews.setOnClickPendingIntent(R.id.btn_notification_next, nextPendingIntent);//通知栏控制器关闭按钮广播操作Intent intentClose = new Intent(CLOSE);PendingIntent closePendingIntent = PendingIntent.getBroadcast(this, 0, intentClose, 0);//为close控件注册事件remoteViews.setOnClickPendingIntent(R.id.btn_notification_close, closePendingIntent);}
目前通知栏上看到的按钮只有四个,因为播放和暂停是一个按钮,到时候可以根据MediaPlayer的播放状态做进一步的处理,上面四个按钮,点击之后会发送一个广播,既然有广播,那自然要有一个广播接收器,就好比,你到淘宝上买衣服,别人给你发货了,你总要设置一个收货地址吧。这是一个道理的。至于广播接收器,可以写在Service里面,作为一个内部类使用。那么先创建这个内部类。
/*** 广播接收器 (内部类)*/public class MusicReceiver extends BroadcastReceiver {public static final String TAG = "MusicReceiver";@Overridepublic void onReceive(Context context, Intent intent) {//UI控制UIControl(intent.getAction(), TAG);}}
然后来看看UIControl方法。
/*** 页面的UI 控制 ,通过服务来控制页面和通知栏的UI** @param state 状态码* @param tag*/private void UIControl(String state, String tag) {switch (state) {case PLAY:BLog.d(tag,PLAY+" or "+PAUSE);break;case PREV:BLog.d(tag,PREV);break;case NEXT:BLog.d(tag,NEXT);break;case CLOSE:BLog.d(tag,CLOSE);break;default:break;}}
对应四个通知栏的按钮,这是是作为广播的接收。但是要实际收到,还要注册才行。
所以要注册动态广播。
/*** 注册动态广播*/private void registerMusicReceiver() {musicReceiver = new MusicReceiver();IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(PLAY);intentFilter.addAction(PREV);intentFilter.addAction(NEXT);intentFilter.addAction(CLOSE);registerReceiver(musicReceiver, intentFilter);}
在这里你可以发现,我对四个值进行了拦截过滤,也就是说当我点击通知栏的上一曲按钮时,会发送动作名为PREV的广播,而这个时候MusicReceiver拦截到PREV的广播,传递给onReceive。然后在onReceive对不同的动作做不同的处理,目前我只是打印了日志而已。
现在你可以将showNotification方法中的如下代码删除掉。
RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.notification);
然后在Service中的onCreate中调用。
@Overridepublic void onCreate() {super.onCreate();initRemoteViews();//注册动态广播registerMusicReceiver();showNotification();BLog.d(TAG, "onCreate");}
initRemoteViews 方法一定要在 showNotification之前调用,否则你就等着null Object 然后APP崩溃吧。
在服务销毁的时候要解绑广播接收者
@Overridepublic void onDestroy() {super.onDestroy();if (musicReceiver != null) {//解除动态注册的广播unregisterReceiver(musicReceiver);}}
下面运行一下,日志如下:
现在通知栏的按钮点击事件就已经监听到了,下面做通知栏的点击事件。
② 通知栏点击监听
只要是通知栏按钮以外的点击都属于通知栏的点击,这个要区分开,别搞混了。在写代码要想清楚一点,当我们点击通知栏的时候,要进入那个页面,我仔细观察过其他音乐APP的这个点击通知栏的效果,是从那个页面切换到后台,下次点击通知栏时就进入到那个页面,也就是说它点击跳转的页面是动态的,所以不能是写死的。有了这个业务需求那么就可以开始写代码了。这里也是需要用到广播的,只不过不再是写内部类了。在com.llw.goodmusic下面新建一个receiver的包,然后创建NotificationClickReceiver,里面的两个可以不用勾选。
创建好之后,打开AndroidManifest.xml你会看到如下代码:
<receiver android:name=".receiver.NotificationClickReceiver"/>
下面进入MusicService中,
//点击整个通知时发送广播Intent intent = new Intent(getApplicationContext(), NotificationClickReceiver.class);PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0,intent, PendingIntent.FLAG_UPDATE_CURRENT);
然后通过.setContentIntent(pendingIntent)设置进去,如下图所示
下面进入到NotificationClickReceiver中。
package com.llw.goodmusic.receiver;import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.llw.goodmusic.utils.BLog;/*** 通知点击广播接收器 跳转到栈顶的Activity ,而不是new 一个新的Activity** @author llw*/
public class NotificationClickReceiver extends BroadcastReceiver {public static final String TAG = "NotificationClickReceiver";@Overridepublic void onReceive(Context context, Intent intent) {BLog.d(TAG,"通知栏点击");}
}
然后运行,运行之后点击通知栏,再看日志打印,如下所示:
③ 通知栏业务处理
在上面已经实现了通知栏的点击监听了,下面就要开始进行业务逻辑的处理了。先解决通知栏的点击业务处理,再解决通知栏按钮的点击处理,打开AndroidManager,注意这是之前我自己写的,不是系统的。在里面增加
/*** 弱引用*/private static WeakReference<Activity> activityWeakReference;private static Object activityUpdateLock = new Object();/*** 得到当前Activity* @return*/public static Activity getCurrentActivity() {Activity currentActivity = null;synchronized (activityUpdateLock){if (activityWeakReference != null) {currentActivity = activityWeakReference.get();}}return currentActivity;}/*** 设置当前Activity* @return*/public static void setCurrentActivity(Activity activity) {synchronized (activityUpdateLock){activityWeakReference = new WeakReference<Activity>(activity);}}
然后进入到BasicApplication中,在onCreate中写入:
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {@Overridepublic void onActivityCreated(Activity activity, Bundle savedInstanceState) {}@Overridepublic void onActivityStarted(Activity activity) {}@Overridepublic void onActivityResumed(Activity activity) {ActivityManager.setCurrentActivity(activity);}@Overridepublic void onActivityPaused(Activity activity) {}@Overridepublic void onActivityStopped(Activity activity) {}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Overridepublic void onActivityDestroyed(Activity activity) {}});
通过上面得代码就可以得到栈顶的Activity,那么怎么来使用这个Activity呢,进入到NotificationClickReceiver
@Overridepublic void onReceive(Context context, Intent intent) {BLog.d(TAG,"通知栏点击");//获取栈顶的ActivityActivity currentActivity = ActivityManager.getCurrentActivity();intent = new Intent(Intent.ACTION_MAIN);intent.addCategory(Intent.CATEGORY_LAUNCHER);intent.setClass(context, currentActivity.getClass());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);context.startActivity(intent);}
这样就可以实现,点击通知栏时跳转到栈顶的Activity而不是新建一个Activity。
下面就是针对通知栏的信息显示做处理了,首先肯定要根据不同的音乐显示不同的歌曲信息,这一点毋庸置疑。那么这样的话就不能一开始就显示通知栏了,而是在点击播放按钮的时候显示通知栏,当切歌,或者暂停时更新这个通知栏的状态,于是就可以在MusicService中写入一个这样的方法。
/*** 初始化通知*/private void initNotification() {String channelId = "play_control";String channelName = "播放控制";int importance = NotificationManager.IMPORTANCE_HIGH;createNotificationChannel(channelId, channelName, importance);//点击整个通知时发送广播Intent intent = new Intent(getApplicationContext(), NotificationClickReceiver.class);PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0,intent, PendingIntent.FLAG_UPDATE_CURRENT);//初始化通知notification = new NotificationCompat.Builder(this, "play_control").setContentIntent(pendingIntent).setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.icon_big_logo).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon_big_logo)).setCustomContentView(remoteViews).setVisibility(NotificationCompat.VISIBILITY_PUBLIC).setAutoCancel(false).setOnlyAlertOnce(true).setOngoing(true).build();}
这里就是把原来的showNotification方法改了一下,把显示通知的代码放到改变通知栏状态的时候使用。当然还是要在onCreate中调用这个方法的。
下面先在MusiceService中定义这些变量
/*** 歌曲间隔时间*/private static final int INTERNAL_TIME = 1000;/*** 歌曲列表*/private static List<Song> mList = new ArrayList<>();/*** 音乐播放器*/public MediaPlayer mediaPlayer;/*** 记录播放的位置*/int playPosition = 0;/*** 通知*/private static Notification notification;/*** 通知栏视图*/private static RemoteViews remoteViews;/*** 通知ID*/private int NOTIFICATION_ID = 1;/*** 通知管理器*/private static NotificationManager manager;/*** 音乐广播接收器*/private MusicReceiver musicReceiver;
然后写入一个更改通知栏样式的方法,每次对音乐进行控制时都会调用。
/*** 更改通知的信息和UI* @param position 歌曲位置*/public void updateNotificationShow(int position) {//播放状态判断if (mediaPlayer.isPlaying()) {remoteViews.setImageViewResource(R.id.btn_notification_play, R.drawable.pause_black);} else {remoteViews.setImageViewResource(R.id.btn_notification_play, R.drawable.play_black);}//封面专辑remoteViews.setImageViewBitmap(R.id.iv_album_cover, MusicUtils.getAlbumPicture(this, mList.get(position).getPath(), 0));//歌曲名remoteViews.setTextViewText(R.id.tv_notification_song_name, mList.get(position).getSong());//歌手名remoteViews.setTextViewText(R.id.tv_notification_singer, mList.get(position).getSinger());//发送通知manager.notify(NOTIFICATION_ID, notification);}
在这个方法里面我调用MusicUtils工具类的getAlbumPicture方法。这个方法我做了一点点改动
改动如下图所示:
下面就是点击播放时的音乐方法了。
/*** 播放*/public void play(int position) {if (mediaPlayer == null) {mediaPlayer = new MediaPlayer();//监听音乐播放完毕事件,自动下一曲mediaPlayer.setOnCompletionListener(this);}//播放时 获取当前歌曲列表是否有歌曲mList = LitePal.findAll(Song.class);if (mList.size() <= 0) {return;}try {//切歌前先重置,释放掉之前的资源mediaPlayer.reset();playPosition = position;//设置播放音频的资源路径mediaPlayer.setDataSource(mList.get(position).path);mediaPlayer.prepare();mediaPlayer.start();//显示通知updateNotificationShow(position);} catch (IOException e) {e.printStackTrace();}}
在上面的播放方法中,首先初始化了MediaPlayer,然后添加了播放完成的监听,这个在后面也是要实现的。然后获取当前的播放位置赋值给成员变量,之后通过位置得到歌曲的路径,通过路径来播放音乐,播放音乐之后将位置传递给显示通知栏的方法,此时通知栏的信息久会更改。
在onCreate方法中添加如下代码,获取本地歌曲数据。
mList = LitePal.findAll(Song.class);
这样做是避免空对象导致APP的崩溃。
接下来就是上一曲的方法
/*** 上一首*/public void previousMusic() {if (playPosition <= 0) {playPosition = mList.size() - 1;} else {playPosition -= 1;}play(playPosition);}
通过播放位置,先判断当前是为第一首歌,是则将播放位置移动到最后一首,不是则直接减一,之后则调用play方法播放上一首歌曲。
下一曲的方法
/*** 下一首*/public void nextMusic() {if (playPosition >= mList.size() - 1) {playPosition = 0;} else {playPosition += 1;}play(playPosition);}
先判断当前是否为最后一首,是的话则从移动到第一首,不是则加一到下一首。然后调用play方法播放下一首歌曲。
暂停继续音乐
/*** 暂停/继续 音乐*/public void pauseOrContinueMusic() {if (mediaPlayer.isPlaying()) {mediaPlayer.pause();} else {mediaPlayer.start();}//更改通知栏播放状态updateNotificationShow(playPosition);}
更改播放状态
最后是关闭通知栏的方法
/*** 关闭音乐通知栏*/public void closeNotification() {if (mediaPlayer != null) {if (mediaPlayer.isPlaying()) {mediaPlayer.pause();}}manager.cancel(NOTIFICATION_ID);}
下面就是调用的地方了
然后还要实现MediaPlayer的音乐播放完成的监听,
public class MusicService extends Service implements MediaPlayer.OnCompletionListener
然后重写onCompletion方法,在里面直接调用nextMusic播放下一曲即可。
/*** 当前音乐播放完成监听** @param mp*/@Overridepublic void onCompletion(MediaPlayer mp) {//下一曲nextMusic();}
下面就要设置通知出现的入口,一般来说是在点击播放按钮,当前有音乐播放时,才会显示通知。然后在layout下面新建一个通用的底部通知布局。
play_control_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><!--底部播放控制布局--><LinearLayoutandroid:id="@+id/lay_bottom"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:background="@color/bottom_bg_color"android:gravity="center_vertical"android:paddingLeft="@dimen/dp_8"android:paddingTop="@dimen/dp_8"android:paddingRight="@dimen/dp_16"android:paddingBottom="@dimen/dp_8"><!-- logo和播放进度 使用相对布局达成覆盖的效果--><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"><!--logo--><com.google.android.material.imageview.ShapeableImageViewandroid:id="@+id/iv_logo"android:layout_width="@dimen/dp_48"android:layout_height="@dimen/dp_48"android:padding="1dp"android:src="@mipmap/icon_music"app:shapeAppearanceOverlay="@style/circleImageStyle"app:strokeColor="@color/white"app:strokeWidth="@dimen/dp_2" /><!--播放进度 自定义View--><com.llw.goodmusic.view.MusicRoundProgressViewandroid:id="@+id/music_progress"android:layout_width="@dimen/dp_48"android:layout_height="@dimen/dp_48"app:radius="22dp"app:strokeColor="@color/gold_color"app:strokeWidth="2dp" /></RelativeLayout><!--歌曲信息 歌名 - 歌手 --><com.google.android.material.textview.MaterialTextViewandroid:id="@+id/tv_song_name"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:ellipsize="marquee"android:focusable="true"android:focusableInTouchMode="true"android:marqueeRepeatLimit="marquee_forever"android:paddingLeft="@dimen/dp_12"android:paddingRight="@dimen/dp_12"android:singleLine="true"android:text="Good Music"android:textColor="@color/white"android:textSize="@dimen/sp_16" /><!--歌曲控制按钮--><com.google.android.material.button.MaterialButtonandroid:id="@+id/btn_play"android:layout_width="@dimen/dp_36"android:layout_height="@dimen/dp_36"android:insetLeft="@dimen/dp_0"android:insetTop="@dimen/dp_0"android:insetRight="@dimen/dp_0"android:insetBottom="@dimen/dp_0"android:theme="@style/Theme.MaterialComponents.Light.NoActionBar"app:backgroundTint="@color/transparent"app:cornerRadius="@dimen/dp_18"app:icon="@mipmap/icon_play"app:iconGravity="textStart"app:iconPadding="@dimen/dp_0"app:iconSize="@dimen/dp_36" /></LinearLayout>
</layout>
其实就是之前LocalMusicActivity的底部布局。
下面进入MainActivity中,
/*** 底部logo图标,点击之后弹出当前播放歌曲详情页*/private ShapeableImageView ivLogo;/*** 底部当前播放歌名*/private MaterialTextView tvSongName;/*** 底部当前歌曲控制按钮, 播放和暂停*/private MaterialButton btnPlay;/*** 自定义进度条*/private MusicRoundProgressView musicProgress;/*** 列表位置*/private int listPosition = 0;
然后在initData中,通过引入的布局绑定控件,并且添加点击监听,下面就可以在
@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.lay_local_music://本地音乐startActivity(new Intent(context, LocalMusicActivity.class));break;case R.id.btn_play:if (mList.size() == 0) {show("没有可播放的音乐,请到 “本地音乐” 进行扫描");return;}musicService.play(listPosition);break;default:break;}}
下面运行测试一波:
④ 运行效果图
结语
现在已经搞定了后台播放和通知栏控制音乐,下一篇就该是通知栏和Activity的双向控制了。
源码地址:Good Music
Android 音乐APP(五)音乐通知栏、后台播放音乐相关推荐
- android mediaplayer 后台播放,Android服务—基于MediaPlayer后台播放音乐
Android服务-基于MediaPlayer后台播放音乐 操作环境:Android Studio 4.0.0.SDK Level 21(版本5.0 Lollipop).Windows 10.集成显卡 ...
- android后台自播放音乐,Android实现后台播放音乐(Service方式)
Android实现后台播放音乐(Service方式) 实现: 在res文件夹下添加raw文件夹,添加mp3/4格式的音乐文件 注意命名规则只能是a-z,0-9,和下划线_ 不能大写字母和- Andro ...
- Android后台播放音乐保活,安卓后台保活黑科技 播放无声音乐
1.准备一段无声的音频,新建一个播放音乐的Service类,将播放模式改为无限循环播放.在其onDestroy方法中对自己重新启动. public class PlayerMusicService e ...
- IOS后台运行 之 后台播放音乐
IOS后台运行 之 后台播放音乐 iOS 4开始引入的multitask,我们可以实现像ipod程序那样在后台播放音频了.如果音频操作是用苹果官方的AVFoundation.framework实现,像 ...
- 解决 后台播放音乐时,设置手机铃声,后台音乐不会暂停
2019独角兽企业重金招聘Python工程师标准>>> 手机后台播放音乐时,设置手机铃声,后台音乐不会暂停,此现象的为设置手机铃声界面,并没要加入播放的foucs机制, 此修改在pa ...
- iOS- 关于AVAudioSession的使用——后台播放音乐
1.前言 •AVAudioSession是一个单例,无需实例化即可直接使用.AVAudioSession在各种音频环境中起着非常重要的作用 •针对不同的音频应用场景,需要设置不同的音频会话分类 1.1 ...
- 后台播放音乐时进来电话或微信视频通话暂停音乐播放 网易云音乐 喜马拉雅...
最近项目中遇到一个问题,app内音乐后台播放时,如果有电话或者微信视频通话进来,app后台音乐还在播放.这样就造成用户体验不好,研究了市面上的音乐播放器,比如网易云音乐就很好的做到了如果有微信视频或者 ...
- Android如何判断当前手机是否正在播放音乐,并获取到正在播放的音乐的信息
我想实现如下的场景,判断当前Android手机上是否正在播放音乐,如果是,通过某个特定的手势, 或者点击某个按键,将当前我正在听的音乐共享出去. 第一步,就是判断当前是否有音乐正在播放. 最开始我想得 ...
- Android 音乐APP(一)扫描本地音乐
效果图 音乐APP 扫描本地音乐 前言 正文 ① 新建项目 ② 第三方依赖 ③ 权限和基础配置 ④ 页面设计 ⑤ 权限请求 ⑥ 获取音乐数据 ⑦ 数据显示 结语 前言 这个项目纯粹的就是心血来潮, ...
最新文章
- C++对象模型2——编译器生成构造函数的几种情况
- whireshark过滤器学习与使用
- 后台设置 datakeynames
- TYVJ P1012 火柴棒等式 Label:枚举
- 天上地下,马斯克和贝佐斯终有一战?
- UBUNTU使用五笔98输入法
- python读取excel(xlrd)
- 标准正态分布表完整图 查询_正态分布基本概念及Excel实现
- C语言正交表测试用例,正交表设计用例(简单+实用) - Jackc的个人空间 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...
- android 脚本发短信,Android使用Intent发送短信的实现方法
- SoftICE使用(3)—在VMware中配置远程SoftICE的另一种办法 zz xfocus
- 代码写得很牛逼但UI界面却搞得很丑?来,杨工带你!
- Q1成绩:华为可穿戴设备增幅亮眼,Uber亏损10亿美元!
- 致我们失去但美好回忆的青春
- 电脑长期未用或深度放电,电池欠压充不上电(充电指示灯不亮)
- bootstrap4导航栏居右
- 投稿前如何查询期刊的审稿周期
- [论文阅读笔记69]医学术语标准化-CODER
- Python Opencv 实现鼠标事件(包含一个练习)——事件触发讲解·以及鼠标回调函数的实现
- python bz2模块
热门文章
- 在线二维码生成工具html源码
- VMware 虚拟机 linux执行 ifconfig 命令 eth0没有IP地址(intet addr、Bcast、Mask) UP BROADCAST MULTICAST 问题
- BCIduino社区|HY-BCI Pro多通道科研级脑电放大器接收lsl脑电数据并进行显示
- 光猫及二级路由器Openwrt均开启IPv6,满足双层网络内IPv6的获取
- Chrome浏览器默认全屏启动(非--kiosk模式)
- CocosCreater 接入手Q (QQ小游戏)、小米快游戏 接入指南、脱坑指南
- 黑猴子的家:Kali Linux + Vmware 15 安装操作系统
- WSL 2 网络配置
- 自学python书籍怎么选-python自学Day07(自学书籍python编程从入门到实践)
- 禁止K8S容器内子进程拥有提升权限的能力