总结系列-Android画中画模式
*近做做播放器,有个浮窗播放的需求,两种实现方式,一种是申请浮窗权限,创建浮窗参考 flowWindow,一种是采用画中画模式(8.0以上)
关于画中画
Android 8.0 Oreo(API Level 26)允许活动启动画中画 Picture-in-picture(PIP)模式。PIP 是一种特殊类型的多窗口模式,主要用于视频播放。PIP 模式已经可用于 Android TV,而 Android 8.0 则让该功能可进一步用于其他 Android 设备。
画中画利用 Android 7.0 中的多窗口模式 API 来提供固定的视频叠加窗口。要将画中画添加到您的应用中,您需要注册支持画中画的 Activity、根据需要将 Activity 切换为画中画模式,并确保当 Activity 处于画中画模式时,界面元素处于隐藏状态且视频能够继续播放。
如何使用
声明对画中画的支持
默认情况下,系统不会自动为应用提供画中画支持。要想在应用中支持画中画,您可以通过将 android:supportsPictureInPicture 和 android:resizeableActivity 设置为 true ,在清单中注册视频 Activity。此外,指定您的 Activity 会处理布局配置更改,这样一来,在画中画模式转换期间发生布局更改时,您的 Activity 不会重新启动。
<activity android:name=”VideoActivity”
android:resizeableActivity=”true”
android:supportsPictureInPicture=”true”
android:configChanges=
“screenSize|smallestScreenSize|screenLayout|orientation”
…
低内存设备可能无法使用画中画模式。在应用使用画中画之前,请务必通过调用 hasSystemFeature(PackageManager. FEATURE_PICTURE_IN_PICTURE) 进行检查以确保可以使用画中画。
isSupportPipMode = getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
if(videoPlayer!= null) {
videoPlayer.setSupportPipMode(isSupportPipMode);
}
对单个播放 Activity 使用画中画模式
要确保将单个 Activity 用于视频播放请求并根据需要进入或退出画中画模式,请在清单中将 Activity 的 android:launchMode 设置为 singleTask:
<activity android:name=”VideoActivity”
…
android:supportsPictureInPicture=”true”
android:launchMode=”singleTask”
…
在您的 Activity 中,替换 onNewIntent() 并处理新的视频,从而根据需要停止任何现有的视频播放。
将您的 Activity 切换到画中画模式
要进入画中画模式,Activity 必须调用 enterPictureInPictureMode()。例如,以下代码会在用户点击应用界面中的专用按钮时,将 Activity 切换到画中画模式:
/**
* 进入画中画模式
*/
private PictureInPictureParams.Builder mPictureInPictureParamsBuilder;
private void enterPiPMode() {
if (videoPlayer == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
videoPlayer.setIsInPictureInPictureMode(true);
if (mPictureInPictureParamsBuilder == null) {
mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder();
}
// Calculate the aspect ratio of the PiP screen. 计算video的纵横比
mVideoWith = videoPlayer.getCurrentVideoWidth();
mVideoHeight = videoPlayer.getCurrentVideoHeight();
if (mVideoWith != 0 && mVideoHeight != 0) {
//设置param宽高比,根据宽高比例调整初始参数
Rational aspectRatio = new Rational(mVideoWith, mVideoHeight);
mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio);
}
//进入pip模式
enterPictureInPictureMode(mPictureInPictureParamsBuilder.build());
}
}
进入 PIP 模式的*常见流程如下:
1. 从按钮触发
* onClicked (View),onOptionsItemSelected (MenuItem) 等等。
2. 有意的离开您的应用程序触发
* onUserLeaveHint ( )
3. 从返回触发
* onBackPressed ( )
在画中画期间处理界面
当 Activity 进入或退出画中画模式时,系统会调用 Activity. onPictureInPictureModeChanged() 或 Fragment. onPictureInPictureModeChanged()。
您应替换这些回调以重新绘制 Activity 的界面元素。请注意,在画中画模式下,您的 Activity 会在一个小窗口中显示。在画中画模式下,用户可能看不清小界面元素的详细信息,因此不会与这些界面元素互动。界面*简的视频播放 Activity 可提供出色的用户体验。Activity 应仅显示视频播放控件。在 Activity 进入画中画模式之前移除其他界面元素,并在 Activity 再次变为全屏时恢复这些元素:
@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
if (isInPictureInPictureMode) {
// Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
} else {
// Restore the full-screen UI.
…
}
}
在画中画模式下继续播放视频
当您的 Activity 切换到画中画模式时,系统会将该 Activity 置于暂停状态并调用 Activity 的 onPause() 方法。如果该 Activity 在画中画模式下暂停,则视频播放不得暂停,而应继续播放。
在 Android 7.0 及更高版本中,当系统调用 Activity 的 onStop() 时,您应暂停视频播放;当系统调用 Activity 的 onStart() 时,您应恢复视频播放。这样一来,您就无需在 onPause() 中检查应用是否处于画中画模式,只需继续播放视频即可。如果您必须在 onPause() 实现中暂停播放,请通过调用 isInPictureInPictureMode() 检查画中画模式并相应地处理播放情况,例如:
@Override
public void onPause() {
// If called while in PIP mode, do not pause playback
if (isInPictureInPictureMode()) {
// Continue playback
…
} else {
// Use existing playback logic for paused Activity behavior.
…
}
}
切换视频/播放下一个时动态调画中画整宽高比例
/**
* 视频尺寸变化(上一个下一个时),动态调整PIP 宽高比
*
* @param with video宽度(非界面宽度)
* @param height video高度(非界面高度)
*/
private void videoSizeChange(int with, int height) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (height != mVideoHeight || mVideoWith != with)) {
mVideoWith = with;
mVideoHeight = height;
if (mPictureInPictureParamsBuilder != null && mVideoWith != 0 && mVideoHeight != 0) {
//设置param宽高比,根据快高比例调整初始参数
Rational aspectRatio = new Rational(mVideoWith, mVideoHeight);
mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio);
//设置更新PictureInPictureParams
setPictureInPictureParams(mPictureInPictureParamsBuilder.build());
}
}
}
进阶使用
添加自定义按钮:
方式一: 通过MediaSession达到如下图效果
(此处有关videoPlayer相关代码根据自己播放器灵活代入,仅供参考)
当 Activity 进入画中画模式后,它默认没有获得输入焦点。要在画中画模式下接收输入事件,请使用 MediaSession.setCallback() 。如需详细了解如何使用 setCallback(),请参阅显示“ 正在播放 ”卡片。
首先在进入小窗前初始化MediaSessionCompat
private MediaSessionCompat mSession;
public static final long MEDIA_ACTIONS_PLAY_PAUSE = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE;
public static final long MEDIA_ACTIONS_ALL = MEDIA_ACTIONS_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
private void initializeMediaSession() {
mSession = new MediaSessionCompat(this, TAG);
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setActive(true);
MediaControllerCompat.setMediaController(this, mSession.getController());
MediaMetadataCompat metadata = new MediaMetadataCompat.Builder().build();
mSession.setMetadata(metadata);
MediaSessionCallback mMediaSessionCallback = new MediaSessionCallback(videoPlayer);
mSession.setCallback(mMediaSessionCallback);
int state = videoPlayer.getCurrentState() == GSYVideoView.CURRENT_STATE_PLAYING ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
updatePlaybackState(state, MEDIA_ACTIONS_ALL, 0, 0);
}
在MediaSessionCompat.Callback中设置自己的播放器逻辑响应
private class MediaSessionCallback extends MediaSessionCompat.Callback {
private LocalListVideoPlayer movieView;
private int indexInPlaylist;
public MediaSessionCallback(LocalListVideoPlayer movieView) {
this.movieView = movieView;
indexInPlaylist = 1;
}
@Override
public void onPlay() {
super.onPlay();
movieView.getGSYVideoManager().start();
movieView.setIsInPictureInPictureMode(true);
movieView.setCurrentState(GSYVideoView.CURRENT_STATE_PLAYING);
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING, 0, 0);
}
@Override
public void onPause() {
super.onPause();
movieView.getGSYVideoManager().pause();
movieView.setIsInPictureInPictureMode(true);
movieView.setCurrentState(GSYVideoView.CURRENT_STATE_PAUSE);
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED, 0, 0);
}
@Override
public void onSkipToNext() {
super.onSkipToNext();
movieView.playNext();
}
@Override
public void onSkipToPrevious() {
super.onSkipToPrevious();
movieView.playLast();
}
}
//更新按钮操作
private void updatePlaybackState(@PlaybackStateCompat.State int state, int position, int mediaId) {
if (mSession.getController().getPlaybackState() != null) {
long actions = mSession.getController().getPlaybackState().getActions();
updatePlaybackState(state, actions, position, mediaId);
}
}
//初始化setPlaybackState
private void updatePlaybackState(@PlaybackStateCompat.State int state, long playbackActions, int position, int mediaId) {
if (mSession != null) {
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder()
.setActions(playbackActions)
.setActiveQueueItemId(mediaId)
.setState(state, position, 1.0f);
mSession.setPlaybackState(builder.build());
}
}
在自己播放器状态更新时更新界面元素
@Override
public void onVideoStart() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if(isInPictureInPictureMode()) {
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING, 0, 0);
}
}
}
@Override
public void onVideoPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if(isInPictureInPictureMode()) {
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED, 0, 0);
}
}
}
方式2: 自定义按钮 (推荐)
(注意,按钮不超过三个,位置不可调节)
您还可以通过在进入画中画模式之前构建 PictureInPictureParams(使用 PictureInPictureParams.Builder. setActions())来明确指定自定义操作,并使用 enterPictureInPictureMode(android.app.PictureInPictureParams) 或 setPictureInPictureParams(android.app.PictureInPictureParams) 在进入画中画模式时传递这些参数。
首先自定义按钮初始化或刷新
private BroadcastReceiver mReceiver;
private static final String ACTION_MEDIA_CONTROL = “media_control”;
private static final String EXTRA_CONTROL_TYPE = “control_type”;
private static final int CONTROL_TYPE_PLAY = 1;
private static final int CONTROL_TYPE_PAUSE = 2;
private static final int CONTROL_TYPE_LAST = 3;
private static final int CONTROL_TYPE_NEXT = 4;
private static final int REQUEST_TYPE_PLAY = 1;
private static final int REQUEST_TYPE_PAUSE = 2;
private static final int REQUEST_TYPE_LAST = 3;
private static final int REQUEST_TYPE_NEXT = 4;
//进入画中画前判断状态,调用initPictureInPictureActions
private void initPictureInPictureActions() {
//int state = videoPlayer.getCurrentState() == GSYVideoView.CURRENT_STATE_PLAYING ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
//STATE_PLAYING = 3 ; STATE_PAUSED = 2
if (videoPlayer.getCurrentState() == GSYVideoView.CURRENT_STATE_PLAYING) {
updatePictureInPictureActions(R.drawable.gsy_play_video_icon_pause, “”, CONTROL_TYPE_PLAY, REQUEST_TYPE_PLAY);
} else {
updatePictureInPictureActions(R.drawable.gsy_play_video_icon_play, “”, CONTROL_TYPE_PAUSE, REQUEST_TYPE_PAUSE);
}
}
/**
* 刷新自定义按钮 (若是初始化,注意区分进入画中画前onpause状态)
*
* @param iconId
* @param title
* @param controlType
* @param requestCode 注意!! 每个intent的requestCode必须不一样
*/
void updatePictureInPictureActions(@DrawableRes int iconId, String title, int controlType, int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (mPictureInPictureParamsBuilder == null) {
mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder();
}
final ArrayList<RemoteAction> actions = new ArrayList<>();
// This is the PendingIntent that is invoked when a user clicks on the action item. You need to use distinct request codes for play and pause, or the PendingIntent won’t be updated.
//上一个
final PendingIntent intentLast = PendingIntent.getBroadcast(this, REQUEST_TYPE_NEXT, new Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, CONTROL_TYPE_LAST), 0);
actions.add(new RemoteAction(Icon.createWithResource(this, R.drawable.gsy_play_video_icon_last), “”, “”, intentLast));
//暂停/播放
final PendingIntent intentPause = PendingIntent.getBroadcast(this, requestCode, new Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, controlType), 0);
actions.add(new RemoteAction(Icon.createWithResource(this, iconId), title, title, intentPause));
//下一个
final PendingIntent intentNext = PendingIntent.getBroadcast(this, REQUEST_TYPE_LAST, new Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, CONTROL_TYPE_NEXT), 0);
actions.add(new RemoteAction(Icon.createWithResource(this, R.drawable.gsy_play_video_icon_next), “”, “”, intentNext));
mPictureInPictureParamsBuilder.setActions(actions);
// This is how you can update action items (or aspect ratio) for Picture-in-Picture mode. Note this call can happen even when the app is not in PiP mode.
setPictureInPictureParams(mPictureInPictureParamsBuilder.build());
}
}
响应按钮发出的intent
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
if (videoPlayer != null) {
isInPIPMode = isInPictureInPictureMode;
videoPlayer.setIsInPictureInPictureMode(isInPIPMode);
}
//自定义action形式
if (isInPictureInPictureMode) {
// Starts receiving events from action items in PiP mode.
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || !ACTION_MEDIA_CONTROL.equals(intent.getAction())) {
return;
}
// This is where we are called back from Picture-in-Picture action
final int controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0);
try {
switch (controlType) {
case CONTROL_TYPE_PLAY:
videoPlayer.getGSYVideoManager().start();
videoPlayer.setIsInPictureInPictureMode(true);
videoPlayer.setCurrentState(GSYVideoView.CURRENT_STATE_PLAYING);
break;
case CONTROL_TYPE_PAUSE:
videoPlayer.getGSYVideoManager().pause();
videoPlayer.setIsInPictureInPictureMode(true);
videoPlayer.setCurrentState(GSYVideoView.CURRENT_STATE_PAUSE);
break;
case CONTROL_TYPE_LAST:
videoPlayer.playLast();
break;
case CONTROL_TYPE_NEXT:
videoPlayer.playNext();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
registerReceiver(mReceiver, new IntentFilter(ACTION_MEDIA_CONTROL));
} else {
// We are out of PiP mode. We can stop receiving events from it.
unregisterReceiver(mReceiver);
mReceiver = null;
}
}
当播放状态改变时更新按钮功能
videoPlayer.setLocalPlayerCallback(new LocalListVideoPlayer.LocalPlayerCallback() {
@Override
public void clickPIPMode() {
enterPiPMode();
}
@Override
public void OnPrepareVideoSizeChanged(int with, int height) {
videoSizeChange(with, height);
}
@Override
public void surfaceDestroyed() {
handleSurfaceDestroyed();
}
@Override
public void onVideoStart() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//自定义action刷新-开始播放-按钮替换为暂停
updatePictureInPictureActions(R.drawable.gsy_play_video_icon_pause, “”, CONTROL_TYPE_PLAY, REQUEST_TYPE_PLAY);
}
}
@Override
public void onVideoPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//自定义action刷新-暂停播放,按钮替换为开始
updatePictureInPictureActions(R.drawable.gsy_play_video_icon_play, “”, CONTROL_TYPE_PAUSE, REQUEST_TYPE_PAUSE);
}
关于浮窗关闭后仍有声音,无法获取浮窗关闭通知
方式一:监听SurfaceView-surfaceDestroyed()
在官方demo中,采用mediaSession方式, 以surfaceview的 surfaceDestroyed()回调关闭播放器
我采用的gsy播放器(同bilibili播放器),无法监听画中画浮窗关闭,采用如下的方法
在播放界面底层创建一个空的emptySurfaceView,通过callback获知浮窗被手动关闭 (此方法有个缺陷:在锁屏时也会回调此方法)
private SurfaceView emptySurfaceView;
….
emptySurfaceView = findViewById(R.id.emtpy_surface);
emptySurfaceView
.getHolder()
.addCallback(
new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if(mLocalPlayerCallback != null) {
mLocalPlayerCallback.surfaceDestroyed();
}
}
});
方式2:通过进入/退出/关闭画中画VideoActivity的生命周期判断(推荐)
操作画中画时VideoActivity相关生命周期梳理:
进入画中画–onPause
画中画返回全屏–OnResume
关闭画中画–onStop
全屏播放状态下下锁屏/解锁 onPause ,onStop / onStart,onResume
画中画状态下下锁屏/解锁 onStop / onStart
//是否支持pip画中画小窗模式(自行判断赋值时机)
protected boolean isSupportPipMode = false;
//是否已经在画中画模式(自行判断赋值时机)
public boolean isInPIPMode = false;
//是否点击进入过画中画模式–用于判断程序在后台时,由画中画返回全屏后退出,是否启动首页activity,以及onstop配合判断是否点击进入过画中画且在画中画模式
public boolean isEnteredPIPMode = false;
@Override
protected void onResume() {
super.onResume();
//画中画返回全屏会执行onresume
isEnteredPIPMode = false;
}
@Override
protected void onStop() {
super.onStop();
//备注: 在画中画模式下,onStop执行时, 若是关闭画中画,isInPictureInPictureMode()=false ; 若是锁屏,isInPictureInPictureMode()=true ; 判断锁屏isLockPage()一直为false
boolean inPictureInPictureMode = false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
inPictureInPictureMode = isInPictureInPictureMode();
}
if (BuildConfig.DEBUG) {
Log.i(TAG, “onStop — inPictureInPictureMode=” + inPictureInPictureMode + ” ,isEnteredPIPMode=” + isEnteredPIPMode + ” ,isInPIPMode=” + isInPIPMode);
}
if (!inPictureInPictureMode && isInPIPMode && isEnteredPIPMode) {
//满足此条件下认为是关闭了画中画界面
if (BuildConfig.DEBUG) {
Log.w(TAG, “onStop — 判断为PIP下关闭画中画”);
}
handleSurfaceDestroyed();
return;
}
if (inPictureInPictureMode && isInPIPMode && isEnteredPIPMode && videoPlayer != null) {
//满足此条件下认为是画中画模式下锁屏
videoPlayer.onVideoPause();
isPause = true;
if (BuildConfig.DEBUG) {
Log.w(TAG, “onStop — 判断为PIP下锁屏”);
}
}
}
关于开启浮窗关闭后显示在*近任务列表
manifest添加 android:excludeFromRecents=”true”
参考:关于Android TaskAffinity的那些事儿
From Picture-in-Picture activity to Back-Stack activity not working in android?
关于APP进入后台,播放完成后吊起主页activity,主页activity也进入浮窗模式(部分机型偶现)
主页设置 android:supportsPictureInPicture=”false”无效
方案:采用遍历tasks,task.moveToFront() – task.moveToFront(); 避免采用startActivity方法使应用回到前台
参考:Launching Intent from notification opening in picture-in-picture window
public static void moveLauncherTaskToFront(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
assert activityManager != null;
final List<ActivityManager.AppTask> appTasks = activityManager.getAppTasks();
for (ActivityManager.AppTask task : appTasks) {
final Intent baseIntent = task.getTaskInfo().baseIntent;
final Set<String> categories = baseIntent.getCategories();
if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) {
task.moveToFront();
return;
}
}
}
判断获取用户是否关闭了应用画中画模式
当您的应用处于画中画模式时,画中画窗口中的视频播放可能会对其他应用(例如,音乐播放器应用或语音搜索应用)造成音频干扰。为避免出现此问题,请在开始播放视频时请求音频焦点,并处理音频焦点更改通知,如管理音频焦点中所述。如果您在处于画中画模式时收到音频焦点丢失通知,请暂停或停止视频播放。
//音频焦点的监听
protected AudioManager mAudioManager;
mAudioManager = (AudioManager) getActivityContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
/**
* 监听是否有外部其他多媒体开始播放
*/
protected AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
//获得了Audio Focus
onGankAudio();
break;
case AudioManager.AUDIOFOCUS_LOSS:
//失去了Audio Focus,并将会持续很长的时间-暂停音频
onLossAudio();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
//暂时失去Audio Focus,并会很快再次获得
onLossTransientAudio();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
//暂时失去AudioFocus,但是可以继续播放,不过要在降低音量
onLossTransientCanDuck();
break;
}
}
};
问题处理记录:
页面切换进入/退出小窗播放状态处理
小窗关闭监听
10s退出
权限支持判断
自定义按钮状态
吊起主页页面状态错误
创建多个后台
参考:
app: MXplayer: (顶层activity: pipmenuActivity)
————————————————
版权声明:本文为CSDN博主「ZhangQiang-」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011200604/article/details/104701266
300. *长上升子序列(JS实现)
304. 二维区域和检索 – 矩阵不可变(JS实现)
306. 累加数(JS实现)
307. 区域和检索 – 数组可修改(JS实现)
310. *小高度树(JS实现)
309. *佳买卖股票时机含冷冻期(JS实现)
. *大单词长度乘积(JS实现)
用PHP命令行控制脚本
可执行文件
所有的PHP发行版,不论是编译自源代码的版本还是预创建的版本,都在默认情况下带有一个PHP可执行文件。这个可执行文件可以被用来运行命令行的PHP程序。
要在你的系统上找到这个可执行文件,就要遵照下面的步骤:
在Windows操作系统里,它被放在PHP主安装目录下,文件名是php.exe或者(在老版本的PHP里)是php-cli.exe。
在Linux操作系统里,它被保存在PHP安装目录的bin/子目录下。
不论是在哪一个操作系统里,你都需要对它进行测试,以保证它能够正常运行,方法是用-v参数调用它:
shell> /path/to/php -v
PHP 5.0.0 (cli) (built: Jun 1 2005 18:32:10)
Copyright (c) 1997-2004 The PHP Group
Zend Engine v2.0.0, Copyright (c) 1998-2004 Zend Technologies
它应该会返回PHP的版本号。
一个简单的PHP CLI程序
一旦找到了这个CLI可执行文件,你就可以用一个简单的程序来使用一下。创建一个简单的文本文件,其中包含有以下PHP代码,并把它保存为hello.php:
<?php
echo “Hello from the CLI”;
?>
现在,试着在命令行提示符下运行这个程序,方法是调用CLI可执行文件并提供脚本的文件名:
shell> /path/to/phphello.php
Hello from the CLI
使用标准的输入和输出
PHP CLI会定义三个常量,以便让在命令行提示符下与解释器进行交互操作更加容易。这些常量见表格A。
表格A
常量 说明
STDIN 标准的输入设备
STDOUT 标准的输出设备
STDERR 标准的错误设备
你可以在自己的PHP脚本里使用这三个常量,以接受用户的输入,或者显示处理和计算的结果。要更好地理解这一点,可以看看下面的脚本(列表A):
列表A
<?php
// ask for input
fwrite(STDOUT, “Enter your name: “);
// get input
$name = trim(fgets(STDIN));
// write input back
fwrite(STDOUT, “Hello, $name!”);
?>
Look what happens when you run it:
shell> /path/to/phphello.php
Enter your name: Joe
Hello, Joe!
在这个脚本里,fwrite()函数首先会向标准的输出设备写一条消息,询问用户的姓名。然后它会把从标准输入设备获得的用户输入信息读取到一个PHP变量里,并它把合并成为一个字符串。然后就用fwrite()把这个字符串打印输出到标准的输出设备上。
使用命令行自变量
在命令行里输入程序参数来更改其运行方式是很常见的做法。你也可以对CLI程序这样做。PHP CLI带有两个特殊的变量,专门用来达到这个目的:一个是$argv变量,它通过命令行把传递给PHP脚本的参数保存为单独的数组元素;另一个是$ argc变量,它用来保存$argv数组里元素的个数。
用PHP脚本编写一段读取$argv并处理它所含参数的代码是很简单的。试试列表B里的示例脚本,看看它是如何工作的:
列表B
<?php
print_r($argv);
?>
Run this script by passing it some arbitrary values, and check the output:
shell> /path/to/phptest.php chocolate 276 “killer tie, dude!”
Array
( [0] => test.php
[1] => chocolate
[2] => 276
[3] => killer tie, dude!
)
正如你可以从输出的结果看到的,传递给test.php的值会自动地作为数组元素出现在$argv里。要注意的是,$argvis的*个自变量总是脚本自己的名称。
下面是一个更加复杂的例子(列表C):
列表C
<?php
// check for all required arguments
// first argument is always name of script!
if ($argc != 4) {
die(“Usage: book.php <check-in-date> <num-nights> <room-type>/n”);
}
// remove first argument
array_shift($argv);
// get and use remaining arguments
$checkin = $argv[0];
$nights = $argv[1];
$type = $argv[2];
echo “You have requested a $type room for $nights nights, checking in on $checkin. Thank you for your order!/n”;
?>
下面是其用法的示例:
shell> /path/to/phpbook.php 21/05/2005 7 single
You have requested a single room for 7 nights, checking in on 21/05/2005. Thank you for your order!
在这里,脚本首先会检查$argc,以确保自变量的数量符合要求。它然后会从$argv里提取出每一个自变量,把它们打印输出到标准的输出设备上。
注意:你可以用Console_Getopt PEAR类向PHP增加更加复杂的命令行参数。
使用CLI参数
除了用命令行传递PHP脚本参数,你还可以传递PHP CLI参数以更改其工作方式。表格B就是一些重要参数的列表:
表格B
参数 说明
-a 交互式运行Run interactively
-c path 从path读取php的.ini文件
-n 不用读取php的.ini文件就直接运行
-m 列出经过编译的模块
-i 显示有关PHP构建的信息
-l 检查PHP脚本的句法
-s 以彩色方式显示源代码
-w 显示去掉注释之后的源代码
-h 显示帮助
交互模式
你还可以以交互方式使用PHP CLI,也就是输入命令,马上获得结果。要得到这种效果,只需要使用一个参数调用CLI可执行文件就行了,就像下面这样:
shell> /path/to/php -a
你会看到一个空行,你可以在里面输入PHP代码。看看:
shell> /path/to/php -a
Interactive mode enabled
<?php
echo mktime();
1121187283
echo 2+2;
4
exit();
shell>
或者,你可以不使用-a参数就调用CLI可执行文件,直接输入完整的脚本或者代码段。用<Ctrl>-D来结束代码段,并让CLI来执行它。见下面的例子:
shell> /path/to/php
<?php
echo date(“d-M-Y h:i:s”, time());
?>
<Ctrl-D>
12-Jul-2005 06:54:04
这就是PHP的命令行,现在你应该已经对PHP CLI有了足够的了解,并开始使用它了。