編輯:Android開發實例
前言
本章內容為Android開發者指南的 Framework Topics/Multimedia and Camera/Media Playback章節,譯為"媒體播放",版本為Android 4.0 r1,翻譯來自:"呆呆大蝦"。
媒體播放
譯者署名: 呆呆大蝦
譯者微博:http://weibo.com/popapa
版本:Android 4.0 r1
原文
http://developer.android.com/guide/topics/media/mediaplayer.html
在本文中
簡介
Manifest聲明
MediaPlayer的使用
異步准備
狀態管理
釋放MediaPlayer
使用帶MediaPlayer的服務
異步運行
異步錯誤的處理
Wake Lock的使用
作為後台服務運行
Audio Focus的處理
進行清理
對意圖AUDIO_BECOMING_NOISY的處理
從Content Resolver中讀取媒體
關鍵類
MediaPlayer
AudioManager
SoundPool
參閱
JetPlayer
音頻捕獲
Android支持的媒體格式
數據存儲
Android的多媒體框架支持多種通用媒體的播放,因此能夠很容易地在程序中集成音頻、視頻和圖片信息。利用MediaPlayer API,可以播放多種來源的音視頻數據,包括存儲於程序資源(裸資源)中的媒體文件、文件系統中的獨立文件、通過網絡連接讀取的數據流。
本文演示了如何編寫一個媒體播放程序。為了兼顧良好的性能和舒適的用戶體驗,它還實現了播放期間用戶和系統之間的交互。
注意: 只能在標准的輸出設備上播放音頻數據,目前即為移動設備的揚聲器或藍牙耳機。並且不能在通話期間同時播放音頻文件。
簡介
下列類用於在Android框架中播放音視頻:
MediaPlayer
本類是播放音視頻的主要API。
AudioManager
本類管理音頻源和設備的音頻輸出。
Manifest聲明
在開始開發MediaPlayer的應用程序之前,請確保manifest已經正確地聲明了以下相關feature:
· Internet Permission —— 如果正在用MediaPlayer來播放基於網絡的流媒體,應用程序必須請求網絡訪問權限。
<uses-permission android:name="android.permission.INTERNET" />
· Wake Lock Permission —— 如果應用程序需要防止屏幕變暗或處理器休眠,或是用到了MediaPlayer.setScreenOnWhilePlaying()、MediaPlayer.setWakeMode()方法,則必須請求本權限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
媒體播放器的使用
媒體框架中最重要的組件之一就是MediaPlayer類。經過一些很少量的設置,此對象即能夠讀取、解碼並播放音視頻內容。它能支持如下多種不同的媒體來源:
· 本地資源
· 內部URI,比如可能來自Content Resolver
· 外部URL(流)
關於Android支持的媒體格式,請參閱文檔Android支持的媒體格式。
下面的例子展示了如何播放本地以裸資源方式提供的音頻(保存於程序的res/raw/目錄下):
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // 不必調用prepare(); create()會自動調用
這裡的“裸”資源是指系統不會以任何特定方式進行解析的文件。當然,這個資源的內容不應該是原始音頻數據,而應是用所支持的格式正確編碼並格式化過的媒體文件。
下面是如何播放來自系統本地提供的URI資源(比如通過Content Resolver獲取的):
Uri myUri = ....; // 在此初始化Uri
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
下例是播放來自遠程URL的HTTP流:
String url = "http://........"; // 在此指定URL
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // 可能會耗時很長! (需創建緩存等)mediaPlayer.start();
注意:如果通過URL來傳送一個來自在線媒體文件的數據流,則該文件必須支持漸進下載(progressive download)。
警告:因為所引用的文件有可能會不存在,所以使用setDataSource()時必須捕捉並且傳遞IllegalArgumentException和IOException異常。
異步准備
原則上說,MediaPlayer的使用可以非常簡單。不過有一點很重要,請記住還有一些工作必須正確地加入到典型的Android程序中去。比如,因為要讀取並解碼媒體數據,prepare()的調用可能會運行很長時間。因此在執行這種耗時很長的方法時,應該永遠避免從程序的UI線程中調用此類方法。那會導致在方法返回之前用戶界面UI都處於掛起狀態,這樣用戶體驗會十分糟糕,並可能會引發ANR(程序沒有響應)錯誤。即使預計到資源會迅速裝載完畢,也請記住在用戶界面上任何響應時間超過1/10秒的工作都會導致很明顯的停頓,並且會給用戶留下一個程序很慢的印象。
為了避免用戶界面UI線程的掛起,請啟動另一個線程來准備MediaPlayer並在完成後通知主線程。不過這就可能要自行編寫線程邏輯,這也是使用MediaPlayer時的通常做法,利用其prepareAsync()方法,框架提供一種便利的途徑來完成此類任務。這個方法在後台進行媒體的准備工作,並且立即返回。媒體准備完畢後,將會調用MediaPlayer.OnPreparedListener的onPrepared()方法,該Listener通過setOnPreparedListener()指定。
狀態管理
MediaPlayer另一個應該被關注的要點是其狀態模型。也就是說,MediaPlayer擁有一個內部狀態,在編寫代碼時必須時刻注意這個內部狀態,因為播放器在某個給定狀態下只允許進行特定的操作。如果在錯誤的狀態下執行操作,系統可能會拋出異常或導致其它不可預知的現象發生。
MediaPlayer類的文檔中已展示了完整的狀態圖,上面標明了哪些方法會使MediaPlayer轉換狀態。比如,新的MediaPlayer被創建時,處於Idle狀態。這時,應通過調用setDataSource()進行初始化,進入Initialized狀態。然後必須用prepare()或prepareAsync()進行准備工作。待到MediaPlayer准備完畢後,將會進入Prepared狀態,這就意味著可以調用start()來播放媒體了。如狀態圖所示,這時可以調用start()、pause()和seekTo()在Started、Paused和PlaybackCompleted狀態之間進行切換。不過請注意,一旦調用了stop(),在下次MediaPlayer准備好之前就不能再次調用start()了。
在編寫有關MediaPlayer對象的代碼時請時刻牢記狀態圖,因為常見的bug原因就是在錯誤的狀態下調用了不合適的方法。
釋放MediaPlayer
MediaPlayer可能會消耗較多的系統資源。因此應該時刻注意,避免不必要時還維持MediaPlayer實例的運行。應該總是在用完後及時調用release(),以確保所申請的系統資源得到有效釋放。比如,正在使用MediaPlayer時activity收到了一個onStop()調用,這時就必須釋放MediaPlayer,因為activity不與用戶交互時沒必要再保持播放器的運行(除非在後台播放媒體,這會在下節討論)。當然,如果activity再次被激活或者再次被啟動,則需要創建一個新的MediaPlayer並再次准備之後才能恢復播放。
下面是釋放並注銷MediaPlayer的語句:
mediaPlayer.release();
mediaPlayer = null;
舉個例子,假如停止activity時忘記釋放MediaPlayer了,但在activity再次啟動時又創建了一個新的播放器,看看可能會產生的問題。眾所周知,用戶切換屏幕方向(或者其它方式改變設備設置)時,系統默認會重啟activity,這樣系統資源可能會由於用戶在橫向縱向間來回旋轉而很快耗盡。因為每改變一次方向,就會創建一個永遠不會釋放的新MediaPlayer。(關於運行時的重啟,詳見運行時變化的處理。)
如果期望在用戶離開activity後還能繼續播放“後台媒體”,正如系統內置音樂播放器那樣,則需要通過Service來控制MediaPlayer,這在使用帶MediaPlayer的服務中討論。
使用帶MediaPlayer的服務
如果程序需要在不顯示時還能在後台播放媒體——也就是說期望在用戶操作其它程序時也能繼續播放——那就必須啟動一個Service並從服務中控制MediaPlayer實例。這種情況下應該十分小心,因為用戶和系統都期望運行後台服務的應用應該能與系統其它功能同時運行。如果應用不能滿足這個要求,用戶體驗將會很糟糕。本節描述了應注意的主要問題,並提供解決建議。
異步運行
首先,如同Activity,所有Service默認運行在單個線程中——事實上,如果從同一個應用程序運行activity和服務,它們默認會使用同一個進程(“主進程”)。因此,服務就需要快速處理傳入的意圖,並且響應這些意圖時還不能執行耗時較長的計算工作。如果需要執行繁重的工作或者阻塞調用,必須以異步方式執行這類任務:創建另一個線程來執行,或利用框架提供的異步處理功能。
比如,假設在主線程中用到MediaPlayer,就應該用prepareAsync()來代替prepare(),並實現MediaPlayer.OnPreparedListener以便在准備完畢後得到通知,然後就可以開始播放了。示例如下:
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mMediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mMediaPlayer = ... // 在此初始化
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync();
// 為了不阻塞主線程而異步准備
}
}
/** 由MediaPlayer准備完畢後調用 */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
異步錯誤的處理
在同步操作時,錯誤通常會以異常或錯誤代碼的方式展現出來;但如果用到了異步資源,應該確保應用程序每次都能正確地獲得錯誤通知。在使用MediaPlayer的時候,可以通過實現一個MediaPlayer.OnErrorListener來達到以上目標,當然還要在MediaPlayer實例中進行設定才行:
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;
public void initMediaPlayer() {
// ...在此初始化MediaPlayer...
mMediaPlayer.setOnErrorListener(this);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... 合適地處理 ...
// MediaPlayer已經切換到Error狀態,必須重啟!
}
}
記住這點非常重要:當發生錯誤時,MediaPlayer將切換到Error狀態(關於完整的狀態圖請參閱MediaPlayer類的文檔),再次使用前必須重啟播放器。
Wake Lock的使用
如果應用程序是為後台播放媒體而設計的,那麼即使服務仍在運行,但設備可能會進入休眠狀態。因為設備休眠時Android系統會嘗試節省電力,任何不必要的手機功能將會關閉,包括CPU和WiFi部件。但是,如果服務正在播放音樂或讀取音樂數據流,就需要防止系統對播放進行干擾。
為了確保服務在上述情況下能維持正常運行,必須使用“喚醒鎖”(wake lock)。wake lock是一種通知系統的途徑:表示應用程序需要用到一些手機空閒時也保持可用的功能。
注意: 應該盡量少用wake lock,並且僅當確實需要時才保持鎖定狀態,因為它會顯著減少設備的電池壽命。
為了確保MediaPlayer播放時CPU能夠保持工作,應在初始化MediaPlayer時調用setWakeMode()方法。一旦調用完成,MediaPlayer會在播放過程中保持這個特殊的鎖,並在暫停和停止時釋放該鎖:
mMediaPlayer = new MediaPlayer();
// ... 在此執行其它初始化工作 ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
不過,本例中申請的wake lock只是保證了CPU維持喚醒狀態。如果正在通過網絡讀取流媒體並且用到了Wi-Fi,則應該再同時保持一個WifiLock,該鎖必須手動申請和釋放。因此,假如開始准備一個使用遠程URL的MediaPlayer,就應該創建並申請一個Wi-Fi鎖。例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
如果暫停或停止播放媒體,或者不再需要使用網絡,就應該及時釋放該鎖:
wifiLock.release();
作為後台服務運行
服務經常用於執行一些後台任務,比如讀取郵件、同步數據、下載數據等。這些情況下,用戶不會明顯察覺服務正在運行,甚至可能都不會注意到某些服務曾被中止而過段時間又重新開始運行。
但是就播放音樂的服務而言,顯然這是用戶能明顯察覺的服務,任何中斷都會顯著影響到用戶的體驗。此外,該服務還是用戶可能期望與其交互的服務。在這種情況下,此服務應該作為“後台服務”來運行。後台服務保持較高的系統重要性級別——系統幾乎永遠都不會關閉服務,因為服務對於用戶而言至關重要。即使是運行在後台,服務仍必須提供狀態欄通知,以保證用戶知曉服務正在運行,並允許用戶打開與服務交互的activity。
為了把服務切換到後台,必須為狀態欄創建一個Notification,並且從Service中調用startForeground()。例如:
String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
new Intent(getApplicationContext(), MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
"Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);
服務在後台運行期間,此notification將顯示在設備的通知區域。如果用戶選中此通知,系統會提交一個預先已實現的PendingIntent。以下例子將打開一個activity(MainActivity)。
圖1展示了通知如何顯示給用戶:
圖1.後台服務通知的屏幕截圖,左圖是狀態欄的通知圖標,右圖是展開的View。
只有那些確實運行著用戶明顯關注任務的服務,才應該保持為“後台服務”狀態。一旦不再需要,就應該調用stopForeground()進行釋放:
stopForeground(true);
詳情請參閱服務和狀態欄通知的文檔。
Audio Focus的處理
雖然在任一給定時刻只能運行一個activity,Android仍是一個多任務環境。這向使用音頻的應用程序提出了一個特殊的挑戰,因為系統只有一路音頻輸出,但可能會存在多個媒體服務,它們會相互爭奪這個音頻輸出的使用權。Android 2.2之前,沒有什麼內部機制來解決這個問題,某些情況下這可能會導致用戶體驗很糟糕。比如在用戶聽音樂時,其它應用程序需要通知用戶一個非常重要的事件,用戶可能會由於音樂聲音較大而聽不到通知提示音。自Android 2.2開始,系統為應用程序提供了一種使用設備音頻輸出的協調機制。這種機制叫做Audio Focus。
當應用程序需要輸出諸如音樂或通知音之類的音頻時,應該總是提出audio focus請求。一但獲得了focus,就可以自由使用音頻輸出,但應該時刻注意focus的變化情況。一旦接到放棄audio focus的通知,就應該立即關閉音頻或者把音量調低至靜音狀態(正如“ducking”——此標志表明哪一個更合適),並且在再次獲得focus之後才能恢復正常音量播放。
Audio Focus事實上具有良好的協作性。也就是說,雖然期望(強烈建議)應用程序能遵守audio focus規則,但此規則並不是系統強制要求的。如果應用程序需要在失去audio focus後也能以正常音量播放音樂,系統也不會阻止。但是用戶體驗很可能會很糟糕,並且很可能會刪除這種不夠禮貌的應用程序。
要發起audio focus請求,必須調用AudioManager中的requestAudioFocus(),示例如下:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 無法獲取audio focus.
}
requestAudioFocus()的第一個參數是AudioManager.OnAudioFocusChangeListener對象,每次audio focus發生改變時都會調用該對象的onAudioFocusChange()方法。因此,必須在服務和activity中都實現這個接口。例如:
class MyService extends Service
implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) {
// 根據focus的改變進行處理...
}
}
focusChange參數標明了audio focus已經發生改變,它可能會是以下值之一(在AudioManager類中定義了所有下列常數):
· AUDIOFOCUS_GAIN:已經獲得了audio focus。
· AUDIOFOCUS_LOSS:似乎已失去audio focus較長時間了。這時必須停止所有的音頻播放。因為不應該會較長時間地失去focus,這是盡可能多地清理資源的絕好時機。比如應該釋放MediaPlayer。
· AUDIOFOCUS_LOSS_TRANSIENT:暫時失去了audio focus,但應該會馬上取回來。這時必須停止所有的音頻播放工作,但因為可能馬上再次獲得focus,所以可以保持資源。
· AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暫時失去了audio focus,但允許繼續安靜(小聲)地播放音頻,而不是完全關閉音頻。
以下是實現的例子:
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// 恢復播放
if (mMediaPlayer == null) initMediaPlayer();
else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
mMediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
// 長時間失去focus:停止播放並釋放media player
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 暫時失去focus,但必須停止播放
// 可能會很快恢復播放,所以不釋放media player
if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 暫時失去focus,但可以保持較低級別的播放
if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
break;
}
}
請記住audio focus API在API level 8 (Android 2.2)以上版本才可用。假如需要支持較低版本的Android,應該采取向後兼容的方案,使得程序能在獲得支持時使用此功能、未獲支持時則向下平滑過渡。
通過反射機制調用audio focus方法,或者在單獨的類中(叫做AudioFocusHelper)實現全部audio focus功能,就可以獲得良好的向後兼容性。以下是這種類的一個示例:
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAudioManager;
// 在此寫入其它內容,
// 將保存一個接口的引用,用於通知服務focus 已發生改變。
public AudioFocusHelper(Context ctx, /* other arguments here */) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// ...
}
public boolean requestFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
}
public boolean abandonFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
// 服務獲知focus的變動
}
}
僅當檢測到系統運行於API level 8以上版本時,才可以創建AudioFocusHelper類的實例。比如:
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}
進行清理
前面提到過,對象可能會消耗相當多的系統資源,因此應該按需保持其運行並在用完後及時調用release()。請顯式地調用這個清理方法而不要依賴於系統的垃圾回收機制,這點非常重要。因為等到垃圾回收器回收MediaPlayer時可能已經過去相當長的時間了,回收器是只對內存需求敏感的,而對其他媒體相關的資源短缺是不做判斷的。因此,在使用服務的時候,應該總是覆蓋onDestroy()方法,以確保釋放MediaPlayer:
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
除了在關閉時釋放掉之外,還應該總是尋找其它合適的機會來釋放MediaPlayer。例如,當預計到較長時間內無法播放媒體時(比如失去audio focus後),應該明確地釋放已有的MediaPlayer並稍後再重新創建。另一方面,如果只是想停止播放一會兒,則應該保持住MediaPlayer,以避免創建和再次准備的開銷。
對意圖AUDIO_BECOMING_NOISY的處理
在事件到來時,很多編碼優秀的音頻播放程序都會自動停止播放,因為事件會讓音頻輸出產生噪音(通過外部揚聲器播放出來)。比如,用戶用耳機聽音樂時突然把耳機從設備上拔出來,就可能會產生噪音。不過好在這種現象不會自動發生。如果不實現暫停播放,聲音就會從外部揚聲器中傳出,這可能是用戶不願意聽到的。
這種場合下,可以通過處理ACTION_AUDIO_BECOMING_NOISY意圖來確保應用程序停止播放音樂,在manifest中加入以下代碼,可以注冊一個處理意圖的接收器:
<receiver android:name=".MusicIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
以下代碼注冊了MusicIntentReceiver類,用作該意圖的廣播接收器:
public class MusicIntentReceiver implements android.content.BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
if (intent.getAction().equals(
android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
// 通知服務來停止播放
// (比如通過一個意圖)
}
}
}
從Content Resolver中讀取媒體
媒體播放程序中另一個可能有用的功能就是從用戶設備上讀取音樂。通過查詢外部媒體的ContentResolver可以實現這一點:
ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
// 查詢失敗,處理錯誤。
} else if (!cursor.moveToFirst()) {
//設備上不存在媒體文件
} else {
int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
do {
long thisId = cursor.getLong(idColumn);
String thisTitle = cursor.getString(titleColumn);
// ...開始處理...
} while (cursor.moveToNext());
}
按以下步驟與MediaPlayer一起使用:
long id = /* 從某處讀取 */;
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...准備並開始播放...
目前Android已經在只能手機市場已經具有強大的霸主地位,也吸引了越來越多的追捧者。Android的學習也越來越火。但是,報名費用確實大多人望而卻步 一、新建項
可以顯示在的Android任務,通過加載進度條的進展。進度條有兩種形狀。加載欄和加載微調(spinner)。在本章中,我們將討論微調(spinner)。Spinner 用
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個
從本文開始為大家制作一個Android個人理財工具,並把整個開發過程記錄下來,