-
-
Notifications
You must be signed in to change notification settings - Fork 45
[ZH] 2.进阶
目录:
PlayerClient
是播放器的客户端,PlayerService
则是播放器的服务端。在使用 PlayerClient
前,需要先建立与 PlayerService
服务端的连接。
当然,在使用 PlayerClient
连接到服务端前,需要先创建一个 PlayerClient
对象。可以使用静态方法 PlayerClient.newInstance(Context, Class<? extends PlayerService>)
创建一个 PlayerClient
对象。
public static PlayerClient newInstance(
Context context, // Context 对象,不能为 null
Class<? extends PlayerService> playerService // PlayerClient 将要连接到的那个 PlayerService 的 Class 对象,不能为 null
)
其中,第 1 参数是一个 Context
对象,不能为 null
;第 2 个参数是一个 Class
对象,这是 PlayerClient
将要连接到的那个 PlayerService
的 Class
对象,同样不能为 null
。
创建 PlayerClient
对象后,可以调用它的以下方法建立与 PlayerService
的连接:
connect()
connect(PlayerClient.OnConnectCallback callback)
其中,第 2 个 connect
方法可以接收一个 PlayerClient.OnConnectCallback
类型的参数,该参数是一个回调接口,该回调接口会在连接成功或失败时调用,如果你需要知道连接结果,则可以使用该方法。
调用 connect()
方法时,如果 PlayerService
还没有启动或者已经终止,则会启动 PlayerService
,然后与其建立连接。
例:
PlayerClient playerClient = PlayerClient.newInstance(context, MyPlayerService.class);
playerClient.connect(new PlayerClient.OnConnectCallback() {
@Override
public void onConnected(boolean success) {
// DEBUG
Log.d("DEBUG", "connect result: " + success);
}
});
此外,还可以使用 PlayerClient
的 isConnected()
来检查连接结果,如果已经连接成功,则该方法会返回 true
,如果没有连接、连接失败或者已断开连接,则该方法会返回 false
。
可以调用 PlayerClient
的以下方法来设置一个新的播放列表:
-
setPlaylist(Playlist playlist)
:设置一个新的播放列表。 -
setPlaylist(Playlist playlist, boolean play)
:设置一个新的播放列表,并决定是否立即播放列表中的第 0 首音乐。 -
setPlaylist(Playlist playlist, int position, boolean play)
:设置一个新的播放列表,同时使用position
参数指定要播放的歌曲的位置,并决定是否立即播放第position
首音乐(position
是歌曲在播放列表中的索引,从0
开始计算)。
注意!不要设置过大的播放列表(建议在 1000 以内),过大的播放列表会导致 Binder
崩溃!
连接成功后,可以调用 PlayerClient
的以下方法获取播放器的播放列表:
-
getPlaylist(PlaylistManager.Callback callback)
:获取当前的播放队列。 -
getPlaylistSize()
:获取播放列表的大小(包含的歌曲的数量)。
例:
playerClient.getPlaylist(new PlaylistManager.Callback() {
@Override
public void onFinished(Playlist playlist) {
// ...
}
});
连接成功后,可以调用 PlayerClient
的以下方法编辑播放器的播放列表:
-
setNextPlay(MusicItem musicItem)
:设置下一首播放的歌曲。如果列表中已包含指定歌曲,则会将它移动到下一首要播放的位置,否则会将MusicItem
插入到下一首要播放的位置。 -
insertMusicItem(int position, MusicItem musicItem)
:将一首歌曲插入到播放列表的指定位置。如果播放列表中已包含指定歌曲,则会将它移动到position
位置。 -
appendMusicItem(MusicItem musicItem)
:将一首歌曲追加到播放列表的末尾。如果播放列表中已包含指定歌曲,则会将它移动到播放列表的末尾。 -
moveMusicItem(int fromPosition, int toPosition)
:将播放列表中fromPosition
位置处的歌曲移动到toPosition
位置。 -
removeMusicItem(MusicItem musicItem)
:从播放列表中移除指定歌曲。如果播放列表中不包含该歌曲,则忽略本次调用。
如果你需要监听播放列表的改变,可以调用 PlayerClient
的 addOnPlaylistChangeListener(Player.OnPlaylistChangeListener listener)
注册一个 Player.OnPlaylistChangeListener
监听器来监听播放列表的改变。当播放列表发生改变时,该监听器的 onPlaylistChanged(PlaylistManager playlistManager, int position)
方法会被调用,其中,PlaylistManager
可以用来获取最新的播放列表,参数 position
是当前正在播放的歌曲在播放列表中的位置(从 0 开始计数)。
例:
playerClient.addOnPlaylistChangeListener(new Player.OnPlaylistChangeListener() {
@Override
public void onPlaylistChanged(PlaylistManager playlistManager, int position) {
// get fresh playlist
playlistManager.getPlaylist(new PlaylistManager.Callback() {
@Override
public void onFinished(Playlist playlist) {
// ...
}
});
// ...
}
});
如果你需要在监听到播放列表改变时获取最新的播放列表,可以使用 PlaylistLiveData
来监听并获取最新的播放列表。使用 PlaylistLiveData
的好处是无需担心会发生内存泄露,而且与使用监听器相比,开发者可以编写更少的代码。
例:
PlaylistLiveData playlistLiveData = new PlaylistLiveData();
playlistLiveData.init(playerClient);
playlistLiveData.observe(lifecycleOwner, new Observer<Playlist>() {
@Override
public void onChanged(Playlist playlist) {
// ...
}
});
可以调用 PlayerClient
的以下方法来控制播放器:
-
play()
:播放 -
pause()
:暂停 -
playPause()
:播放/暂停 -
playPause(int position)
:播放/暂停列表中指定位置处的音乐 -
stop()
:停止 -
seekTo(int progress)
:调整播放位置 -
skipToPrevious()
:上一曲 -
skipToNext()
:下一曲 -
skipToPosition(int position)
:播放列表中 position 处的音乐,如果正在播放,则忽略本次调用 -
fastForward()
:快进 -
rewind()
:快退
更多方法,请查看 API 文档
可以调用 PlayerClient
的以下方法对播放器进行配置:
-
setPlayMode(PlayMode playMode)
:设置播放模式(共有 3 种模式:顺序播放、单曲循环、随机播放) -
setOnlyWifiNetwork(boolean onlyWifiNetwork)
:设置是否只允许在 WiFi 网络下播放音乐(默认为false
) -
setSoundQuality(SoundQuality soundQuality)
:设置播放器的首选音质 -
setAudioEffectEnabled(boolean enabled)
:设置是否启用音频特效(如:均衡器,默认为false
) -
setAudioEffectConfig(Bundle config)
:修改音频特效的配置
注意!“仅 WiFi 网络播放”、“播放器的首选音质” 与 “音频特效” 功能需要开发者配合 PlayerService
自行实现,具体请参考:自定义 PlayerService
关于音频特效,请查看 EqualizerActivity
PlayerClient
提供了众多方法来帮助开发者监听播放器的状态:
-
addOnPlaybackStateChangeListener(PlayerClient.OnPlaybackStateChangeListener listener)
:监听播放器状态(粗粒度) -
addOnPlaybackStateChangeListener(Player.OnPlaybackStateChangeListener listener)
:监听播放器状态(细粒度) -
addOnPrepareListener(Player.OnPrepareListener listener)
:监听播放器准备(prepare)状态 -
addOnBufferedProgressChangeListener(Player.OnBufferedProgressChangeListener listener)
:监听播放器缓存进度 -
addOnAudioSessionChangeListener(PlayerClient.OnAudioSessionChangeListener listener)
:监听播放器的audio session id
-
addOnPlayingMusicItemChangeListener(Player.OnPlayingMusicItemChangeListener listener)
:监听正在播放的MusicItem
-
addOnPlaylistChangeListener(Player.OnPlaylistChangeListener listener)
:监听播放器正在播放的列表 -
addOnPlayModeChangeListener(Player.OnPlayModeChangeListener listener)
:监听播放器的播放模式 -
addOnSeekCompleteListener(Player.OnSeekCompleteListener listener)
:监听播放器播放进度调整完成事件 -
addOnConnectStateChangeListener(PlayerClient.OnConnectStateChangeListener listener)
:监听PlayerClient
的连接成功断开连接事件 -
addOnStalledChangeListener(Player.OnStalledChangeListener listener)
:监听播放器的stalled
状态。当播放的缓冲区没有足够的数据支持继续播放时,stalled
状态为true
,否则为false
-
addOnRepeatListener(Player.OnRepeatListener listener)
:当将播放模式设置为循环播放(PlayMode.LOOP
)时,会在歌曲播放完,准备再次循环播放时调用该方法
当你不再需要某个监听器时,可以调用对应的 removeXxx
方法将其移除。
例:
Player.OnPlaybackStateChangeListener listener = new Player.OnPlaybackStateChangeListener() {
// ...
};
// 添加监听器:Player.OnPlaybackStateChangeListener
playerClient.addOnPlaybackStateChangeListener(listener);
// 移除监听器:Player.OnPlaybackStateChangeListener
playerClient.removeOnPlaybackStateChangeListener(listener);
上面这些方法各自都有 1 个接收 LifecycleOwner
作为第 1 个参数的重载方法,使用这些重载方法添加的监听器会在 LifecycleOwner
销毁时自动注销以避免内存泄露。
public void addOnPlaybackStateChangeListener(Player.OnPlaybackStateChangeListener listener)
// 接收 LifecycleOwner 作为第一个参数
public void addOnPlaybackStateChangeListener(LifecycleOwner owner,
Player.OnPlaybackStateChangeListener listener)
例:
Player.OnPlaybackStateChangeListener listener = new Player.OnPlaybackStateChangeListener() {
// ...
};
playerClient.addOnPlaybackStateChangeListener(lifecycleOwner, listener);
除了使用监听器来监听播放器的状态外,还可以手动调用 PlayerClient
的各种 getter
方法来获取播放器状态。其中常用的方法有:
-
getPlayingMusicItem()
:获取当前正在播放的音乐。 -
getPlaybackState()
:获取当前播放状态。 -
getPlayPosition()
:获取当前播放列表的播放位置。 -
getPlayProgress()
:获取播放进度(单位:毫秒)。 -
getPlayProgressUpdateTime()
:获取播放进度的更新时间。注意!这是基于SystemClock.elapsedRealtime()
的时间。 -
getPlayMode()
:获取当前播放模式。 -
getErrorCode()
:获取错误码。 -
getErrorMessage()
:获取错误信息。 -
getAudioSessionId()
:获取正在播放的歌曲的 audio session id。如果没有播放任何歌曲,则返回0
。 -
getBufferedProgress()
:获取当前的缓存进度。
提示:这些用于获取播放器状态的方法并不是 “实时” 的。例如,如果你在调用了
skipToNext()
方法后立即去调用getPlayingMusicItem()
方法,那么有很大概率获取到的并不是当前正在播放的歌曲,这是因为像skipToNext()
这类用来控制播放器的方法仅仅只是在发出一条命令后立即返回,并不会等到命令执行完毕后再返回。因此,如果你在skipToNext()
方法返回后立即调用上面这些方法获取播放器状态,由于此时命令可能还没有执行完毕(或者根本没有执行),因此获取到的状态可能并不是你所期望的。如果你需要准确的获取播放器的状态,则应该使用上一节介绍的监听器,这些监听器会在播放器的状态改变时立即被调用。
更多方法,请查看 API 文档
使用监听器来监听播放器状态并更新 UI 是一个非常繁琐的过程。为了简化这个过程,本项目为此提供了一个 PlayerViewModel
。
PlayerViewModel
的使用方法与其他的 ViewModel
区别不大。只需在创建好 PlayerViewModel
对象后调用它的任意一个 init
方法对其进行初始化即可(如:init(Context, PlayerClient)
)。
例:
@Override
protected void onCreate(Bundle savedInstanceState) {
...
PlayerViewModel playerViewModel = new ViewModelProvider(this).get(PlayerViewModel.class);
// 避免重复初始化和创建多余的 PlayerClient 对象
if (playerViewModel.isInitialized()) {
mPlayerClient = playerViewModel.getPlayerClient();
return;
}
// 创建 PlayerClient 对象
mPlayerClient = PlayerClient.newInstance(this, MyPlayerService.class);
// 初始化 PlayerViewModel 对象
playerViewModel.init(this, mPlayerClient);
// 设置在 ViewModel 被销毁时自动断开 PlayerClient 的连接
playerViewModel.setDisconnectOnCleared(true);
}
最后,当不再需要 PlayerClient
对象时,可以调用它的 disconnect()
方法断开与 PlayerService
的连接。PlayerService
会继续保持在后台运行,并不会因所有客户端都断开连接而终止运行。断开连接后的 PlayerClient
对象可以重复使用,你可以再次调用它的 connect()
方法重新建立与播放器的连接。
例:
// 断开连接
mPlayerClient.disconnect();
...
// 断开连接后,可以再次 connect 方法重新建立连接
mPlayerClient.connect();
如果你需要终止 PlayerService
,可以调用 PlayerClient
的 shutdown()
方法,调用该方法后,PlayerService
会被终止,所有客户端的连接都会被断开。如果你希望再次运行播放器,则需要重新调用 connect()
方法进行连接,因为 PlayerService
已被终止了,调用 connect()
方法可以重新启动它。
例:
// 终止 PlayerService
mPlayerClient.shutdown();
// 终止 PlayerService 后,所有客户端的连接都会被断开
// 如需再次运行播放器,需要重新调用 connect() 方法连接并启动 PlayerService
mPlayerClient.connect();
ExoPlayer
提供了对下载与缓存的支持,此框架提供了对 ExoPlayer
的支持,你可以在此框架中快速集成 ExoPlayer
,详情请参阅 Wiki:使用 ExoPlayer
关于下载与缓存,请参考 ExoPlayer
文档:Downloading media
本项目提供了 “睡眠定时器” 功能,你可以设置一个定时时间(单位:毫秒),并设置当定时时间到时要执行的操作(暂停播放、停止播放或者关闭 PlayerService
)。
可以调用 PlayerClient
的以下方法启动或停止睡眠定时器:
-
startSleepTimer(long time)
:启动睡眠定时器,当定时时间到时将暂停播放。 -
startSleepTimer(long time, SleepTimer.TimeoutAction action)
:启动睡眠定时器,当定时时间到时执行由action
参数指定的操作。共有 3 种操作:-
SleepTimer.TimeoutAction.PAUSE
:当定时时间到时暂停播放。 -
SleepTimer.TimeoutAction.STOP
:当定时时间到时停止播放。 -
SleepTimer.TimeoutAction.SHUTDOWN
:当定时时间到时关闭PlayerService
。
-
-
cancelSleepTimer()
:取消睡眠定时器。
可以调用 PlayerClient
的以下方法获取睡眠定时器的状态:
-
isSleepTimerStarted()
:查询睡眠定时器是否已启动,如果已经启动,则返回true
,否则返回false
-
getSleepTimerTime()
:获取睡眠定时器的定时时间(单位:毫秒)。该方法的返回值只在睡眠定时器已启动时才有意义。 -
getSleepTimerStartedTime()
:获取睡眠定时器的启动时间(单位:毫秒, 基于SystemClock.elapsedRealtime()
)。该方法的返回值只在睡眠定时器已启动时才有意义。 -
getSleepTimerElapsedTime()
:获取睡眠定时器已经走过的时间(单位:毫秒)。该方法的返回值只在睡眠定时器已启动时才有意义。
此外,还可以调用 PlayerClient
的以下方法注册一个监听器来监听睡眠定时器的状态:
-
addOnSleepTimerStateChangeListener(SleepTimer.OnStateChangeListener listener)
:监听睡眠定时器的状态。 -
addOnSleepTimerStateChangeListener(LifecycleOwner owner, SleepTimer.OnStateChangeListener listener)
:监听睡眠定时器的状态。额外接收一个LifecycleOwner
,可以避免内存泄露。 -
removeOnSleepTimerStateChangeListener(SleepTimer.OnStateChangeListener listener)
:移除已注册的睡眠定时器状态监听器。
SleepTimer.OnStateChangeListener
监听器的回调方法:
-
onTimerStart(long time, long startTime, SleepTimer.TimeoutAction action)
:当启动睡眠定时器时会调用该方法。 -
onTimerEnd()
:当睡眠定时器正常终止(定时时间到)或者被取消时会调用该方法。
你可以在你的 PlayerService
的 onCreate
方法中调用 setMaxIDLETime(int minutes)
方法设置当前 PlayerService
的最大空闲时间(单位:分钟)。
当播放器 没有 处于 PlaybackState.PLAYING
状态且 preparing
与 stalled
都为 false
时,就将 PlayerService
视为处于空闲状态。当 PlayerService
处于空闲状态的时间超过使用 setMaxIDLETime(int minutes)
方法设置的最长时间时,PlayerService
将自动终止。这可以节省系统资源,并节约电池电量。
默认的最大空闲时间为 -1
。当设置的最大空闲时间小于等于 0
时,将关闭该功能,也就是说默认没有启用该功能。
例:
public class MyPlayerService extends PlayerService {
...
@Override
public void onCreate() {
super.onCreate();
// 设置当前 PlayerService 的最大空闲时间
setMaxIDLETime(10);
...
}
...
}
如果你的音频源不支持 seek
操作,例如直播流(Live Stream),则应该在创建 MusicItem
对象时禁用 seek
操作。
可以在使用 MusicItem.Builder
构建 MusicItem
对象时调用 MusicItem.Builder#setForbidSeek(boolean)
方法禁用 seek
操作。也可以在创建 MusicItem
对象后调用 MusicItem#setForbidSeek(boolean)
方法禁用 seek
操作。
例:
MusicItem liveStream = new MusicItem.Builder()
.setTitle("Live Stream")
.setArtist("Live Stream")
.setDuration(0) // 或者 Integer.MAX_VALUE
.setUri("https://www.example.com/some_live_stream")
.setIconUri("https://www.example.com/icon.png")
// 禁用 seek 操作
.setForbidSeek(true)
.build();
你可以调用以下方法来设置是否忽略音频焦点:
PlayerClient#setIgnoreAudioFocus(boolean ignoreAudioFocus)
PlayerService#setIgnoreAudioFocus(boolean ignoreAudioFocus)
还可以使用以下方法判断是否忽略音频焦点:
例:
public void toggleIgnoreAudioFocus() {
mPlayerClient.setIgnoreAudioFocus(!mPlayerClient.isIgnoreAudioFocus());
}
如果忽略了音频焦点,则在其他音频应用开始播放音频时不会自动暂停播放。
警告!忽略音频焦点后,来电时播放器不会自动暂停播放!如果您想忽略音频焦点,并在来电时自动暂停播放,则需要在运行时请求 android.permission.READ_PHONE_STATE
权限。
例:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
在运行时申请权限:
boolean noPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED;
if (noPermission) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_PHONE_STATE}, requestCode);
}
本项目是基于 MediaSession
框架构建的,并提供了对 MediaSession
框架的兼容。
要点:
-
PlayerService
继承了MediaBrowserServiceCompat
类,因此可以像使用MediaBrowserServiceCompat
那样使用PlayerService
类。 - 可以覆盖
PlayerService
的onCreateMediaSessionCallback()
来提供你自己的MediaSessionCompat.Callback
实现。该方法的返回值类型是PlayerService.MediaSessionCallback
,该类型是MediaSessionCompat.Callback
的子类型。 - 可以调用
PlayerService
的setMediaSessionFlags(int flags)
方法设置MediaSessionCompat
的Flags
。
更多内容,请查看 API 文档
该项目提供了对 AppWidget
的支持,开发者可以在 AppWidgetProvider
中使用 AppWidgetPlayerState
类来获取播放器状态。
为了在播放器状态变化后刷新 AppWidget
,请重载 PlayerService
的 getAppWidget()
方法,并在该方法中返回你的 AppWidgetProvider
的 Class
对象。
例:
public class MyPlayerService extends PlayerService {
// ...
@Nullable
@Override
protected Class<? extends AppWidgetProvider> getAppWidget() {
return ExampleAppWidgetProvider.class;
}
}
如果你的应用中有多个 AppWidget
需要刷新,可以重载 getAppWidgets()
方法。如果重载了 getAppWidgets()
方法并且返回了一个非 null
值,则 getAppWidget()
方法将不再有效。
例:
public class MyPlayerService extends PlayerService {
// ...
@Nullable
@Override
protected List<Class<? extends AppWidgetProvider>> getAppWidgets() {
List<Class<? extends AppWidgetProvider>> appWidgets = new ArraysList<>();
appWidgets.add(ExampleAppWidgetProviderA.class);
appWidgets.add(ExampleAppWidgetProviderB.class);
appWidgets.add(ExampleAppWidgetProviderC.class);
return appWidgets;
}
}
获取播放器状态:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
// 获取播放器状态
AppWidgetPlayerState state = AppWidgetPlayerState.getPlayerState(context, MyPlayerService.class);
if (state == null) {
return;
}
// ...
}
}
当播放器状态改变时,会发送一个 ACTION_PLAYER_STATE_CHANGED
("snow.player.appwidget.action.PLAYER_STATE_CHANGED"
)广播,该广播的 Category
是你的 PlayerService
的完整类名(如 snow.demo.MyPlayerService
)。可以为你的 AppWidgetProvider
添加一个监听该广播的 <intent-filter>
,并在接收到该广播时更新你的 AppWidget
。
注意!由于 Android
限制了隐式广播的发送,因此该方法在高版本 Android
系统上不一定有效,建议优先使用上面介绍的第一种方法。
例:
首先,在你的应用的 AndroidManifest.xml
声明并使用以下权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="snow.player.debug">
<!--声明并使用权限-->
<permission android:name="snow.player.appwidget.permission.UPDATE_APPWIDGET" />
<uses-permission android:name="snow.player.appwidget.permission.UPDATE_APPWIDGET" />
...
</manifest>
然后,为你的 AppWidgetProvider
增加一个 <intent-filter>
元素:
<receiver
android:name=".ExampleAppWidgetProvider"
android:exported="true">
...
<!--增加一个 intent-filter 元素-->
<intent-filter>
<action android:name="snow.player.appwidget.action.PLAYER_STATE_CHANGED" />
<!-- 该 category 的 android:name 是你的 PlayerService 的完整类名 -->
<category android:name="snow.player.debug.MyPlayerService" />
</intent-filter>
</receiver>
最后,在接收到广播时更新 AppWidget
:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
// 接收到广播时更新 AppWidget
if (AppWidgetPlayerState.ACTION_PLAYER_STATE_CHANGED.equals(intent.getAction())) {
AppWidgetManager am = AppWidgetManager.getInstance(context);
onUpdate(context, am, am.getAppWidgetIds(new ComponentName(context, ExampleAppWidgetProvider.class)));
}
}
// ...
}
可以在 AppWidgetProvider
的 onUpdate
方法中使用静态方法 AppWidgetPlayerState.getPlayerState(Context, Class<? extends PlayerService>)
获取到最新的播放器状态。
public static AppWidgetPlayerState getPlayerState(
Context context,
Class<? extends PlayerService> playerService
)
参数说明:
-
context
:Context
对象,不能为null
-
playerService
:你的PlayerService
的 Class 对象,不能为 null
返回值类型:
-
AppWidgetPlayerState
:该类型提供了一系列获取播放器状态的getter
方法,通过该类型即可获取到最新的播放器状态。
例:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
// 获取播放器状态
AppWidgetPlayerState state = AppWidgetPlayerState.getPlayerState(context, MyPlayerService.class);
if (state == null) {
return;
}
// ...
}
}
End