/******************************************************************************************************************
主界面
******************************************************************************************************************/
ScanResultActivity→ScanActivity→MusicCenterActivity。
MusicCenterActivity的主要页面是一个Frament(MainLocalFrament),还有最下面那个R.id.playbar_layout_big(RelativeLayout)是在MusicUtils那里的,因为那个VIew在很多Activity都有显示。
R.id.playbar_more(imageButton)弹出的Dialog或者activity都由MusicUtil.showPopupMenuDialog(final Activity a)负责。
详细的介绍下MainLocalFragment:
布局是fg_main_local.xml:其中include一个actionbarLayout和一个searchbarLayout,还有一个SpecialOverScrollAmigoListView extends AmigoListView;
而主要就是SpecialOverScrollAmigoListView,它的适配器是LocalAdapter extends BaseAdapter。在其getView()中,在生成View时,会根据不同的position去初始化不同的layout。position1是item_local_function.xml(就是六个图标那个),position2是Tilte,就是显示为My music的标题,position3和到create new song list之前都是是item_musicorsinger_layout.xml,就是歌单item,歌单的信息由Adapter中的mArrayList提供数据。create new song list之后还会初始化东西的,但好像不是所以情况都是会初始化吧。
/******************************************************************************************************************
主界面
******************************************************************************************************************/锁屏时显示的音乐界面,最下层的view是锁屏package的,但是上层是music package的。
点击首页的我喜欢的界面是NativeTrackListActivity(这个Activity和NativeSongAcitvity几乎一摸一样,辨别时最好用Log的方式验证),当前歌单没有歌曲时显示导入本地歌曲按钮,点击进去也是一个NativeTrackListActivity。就是说一个任务栈里面有两个NativeTrackListActivity实例。这个Activity比较复杂,因为他承受的多个页面的任务。
如果“”我喜欢“”这页面显示的是一个Fragment,里面是一个expandableList。所以这个Activity的核心成员是NativeTrackListFragment mFragment;Fragment里面有ListView和Adapter,GnMusicDragListView extends AmigoExpandableListView和TrackListAdapter extends BaseExpandableListAdapter。可以设置一个Observer到Adapter中监听数据变化,但是在回调方法中调用Adapter#getGroupCount()总是等于0。而ListView.getChildAt(0)在没有数据的时候,很多时候会显示为not null,但是偶尔会显示为null。这就很奇怪。
比较稳定的是,当查询完毕后,会调用NativeListFragment#TrackQueryHandler#onQueryCompleted().在这里面的cusor可以知道查询到有多少数据,还在这里面执行隐藏headLine和显示headLine的操作。就是将ListView translate上去(view.setY())。
全部选择(批量选择键)就是 TextView NativeTrackListActivity#CBBulk
/**********************************************************************************************************************
MediaPlaybackActivity
*********************************************************************************************************************/
点击PlayingNowLayout进去的Activity是MediaPlaybackActivity。这个contentView有三个,具体哪个不清楚,但是有些还是可以肯定的。
if(sPlayerSwitch){
setContentView(R.layout.media_playback_layout_local);
}else if(sBusinessSwitch ){
setContentView(R.layout.media_playback_layout_business);
}else {
setContentView(R.layout.media_playback_layout);
}
里面会有一个MediaViewPage extends ViewPage mMediaViewPage 和一个ImageView mClearImage,还有其他很多都是共有的,会在updateUI()中初始化。updateUI会在oncreate()调用。initViewPage()用于初始化mMediaViewPage.
其中ViewPage有3个页面,page0是songList,page1是一些歌曲信息等,page2是歌词。MediaVIewPage 中维护了一些比较重要的成员:AmigoListView mSongList;ImageView[] mDotList;MediaViewPage OnMediaPageChangeListener implements OnPageChangeListener mOnChangePageListener;通过MediaViewPager.OnMediaPageChangeListener.addObserver(OnGradualChangeListener observer)去设置监听器监听ViewPage的滑动。
MediaViewPage 在MediaViewPage#initializeViewPager()中装载三个页面。分了三种情况,不同情况使用的布局文件不一样,但是歌词显示页面都是使用同一个页面,就是mLrcView = inflater.inflate(R.layout.lrcview, null);这个页面包括三块,首先是歌词显示View(LrcTextView extends Text),这VIew可以监控滑动和单击事件。并动态重绘View中的歌词在编辑模式时。第二块是歌词控制View(RelativeLayout R.id.lrc_controlzone),单击LrcTextView歌词控制View会消失,进入编辑模式。第三块是歌词滑动时对应的当前歌词对应事件View,是R.id.lrc_identification,这个是一个include,引用的layout是lrcview_identification.xml。
MediaPlaybackActivity还有一个重要的成员就是FrameBgLoader mFrameBgLoader;这个就是管理背景,就是歌曲专辑图片的的显示。就是管理mClearImage。特别是每个歌曲都有专辑图片的时候,还有在线获取的问题,还有文件读取的问题,还有在ViewPage中注册一个监听滑动时设置图片的Alpa,还有在播放另一首音乐时换背景。这个对象在oncreate()中创建,调用在Activity中主动调用FrameBgLoader#init(Activity)初始化,调用FrameBgLoader#setFrame()设置要管理的那个ImageView。监听ViewPage滑动的监听器通过FrameBgLoader#getOnMediaPageChangeListener()获取。与执行专辑图片更新切换相关的代码在MediaPlaybackActivity#LoadBackgroundTask extends Task由Executor执行。
该界面中的控件部分不是MusicUtil初始化的nowPlaying,而是在该Activity中初始化的。在updateUI()中初始化。
底部弹出的更多菜单是play_menu_layout.xml。处理底部菜单栏都是在MediaMenuFunHelper.java中。
/**********************************************************************************************************************
MediaPlaybackActivity
*********************************************************************************************************************/
跟任何界面(包括remoteView)都几乎息息相关 的一个类就是MediaPlaybackService;他是整个音乐APP的枢纽。很多命令都会在该Service中转一次。如无论在哪里按下一首的按钮,都是把命令发到该Service,然后再由该Service发送各种broadCast。接受这些广播的很多都是在Activity中动态注册的Receiver。而Service中也动态注册了很多Receiver,由给Service的register开头的方法中。一个Receiver接受很多Action。
/*******************************************************************************************************
蓝牙相关
******************************************************************************************************/
音频蓝牙传输协议相关的类是BluetoothA2dp。可以主动去连接这个设备,也可以用设置中的蓝牙设置去连接。
蓝牙耳机按键的接收。使用action android:name="android.intent.action.MEDIA_BUTTON"这个可以接收广播。
音乐模块中在Manifest中静态注册了这个接收器MediaButtonIntentReceiver。接收器的优先级是动态注册的比静态注册高,priority值越大,优先级越高。相同优先级的,先注册的优先级高。可以在onReceive中使用abortBroadcast()拦截有序广播。
/*******************************************************************************************************
蓝牙相关
******************************************************************************************************/
/****************************************************************************************************
email音乐选择界面
****************************************************************************************************/
是MusicPickActivity extend是 ListActivity。
布局文件是:music_picker.xml
它的ListView的Adapter是内部类TrackListAdapter extends SimpleCursorAdapter,item使用的Layout是music_picker_item.xml
/****************************************************************************************************
email音乐选择界面
****************************************************************************************************/
/****************************************************************************************************
过滤后同步PlayQueue
****************************************************************************************************/
从GNMediaScanner#sendFinishMsg(int)开始,先使用checkList()从PlayQueue中清除掉已被过滤调用的歌曲.再发送扫描结束的信息private void sendFinishMsg(int scanCnt){
if (null != MusicScanActivity.getScanProgressHandler()) {
if(Looper.myLooper() != Looper.getMainLooper()){
PlayQueue.getInstance().checkList();
}
MusicScanActivity.getScanProgressHandler().removeMessages(MusicScanActivity.MSG_SCAN_FINISH);
Message msge = MusicScanActivity.getScanProgressHandler().obtainMessage();
msge.what = MusicScanActivity.MSG_SCAN_FINISH;
msge.arg1 = scanCnt;
MusicScanActivity.getScanProgressHandler().sendMsg(msge);
}
}
再由MusicScanActivity#ScanProgressHandler处理这消息
case MSG_SCAN_FINISH:
LogUtil.i(TAG, "recv finish msg ");
updateUIWhenFinishScan(msg);
Intent intent = new Intent();
intent.setAction(AppConsts.SERVICE_RELOAD_ACTION);
sendBroadcast(intent);
//记录MediaProvider数据的最大值
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
long i = MediaDBUtils.getMaxDateAdded(getApplicationContext());
MusicPreference.setMaxDateAdded(getApplicationContext(), i);
}
}).start();
break;
最终去到MediaPlaybackService
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
try {
if (mReceiverUnregistered) {
return;
}
String action = intent.getAction();
String cmd = intent.getStringExtra("command");
LogUtil.d(TAG, "mIntentReceiver.onReceive: " + action + "/" + cmd);
if (AppConsts.SERVICE_RELOAD_ACTION.equals(action)) {
LogUtil.i(TAG, "onScannerComplete()");
//如果播放PlayQueue没有歌曲,就是reloadQueue,和saveQueue()对应.通知QueueChange,
//还有META_CHANGE,其中会通知大小桌面控件.
if (getTrackName() == null) {
reloadQueue();
// Gionee <liujl><2013-09-11> modify for CR00889566 begin
if (!mOnlineMusic) {
notifyChange(QUEUE_CHANGED);
notifyChange(META_CHANGED);
}
// Gionee <liujl><2013-09-11> modify for CR00889566 end
}
//如果PlayQueue还有歌曲,且当前歌曲被删除了和正在播放的话,就调用nextSong()播放下一首,
//如果当前歌曲被删除了和是暂停状态的话,就发ACTION_STOP的广播,专门处理删除后暂停状态下.
//的逻辑问题
else
{
if (!MusicUtils.getSongExits(context,getAudioId())) {
boolean bPlay = isPlaying();
reloadQueue();
if (bPlay) {
nextsong();
} else {
Intent it = new Intent();
intent.setAction(MediaPlaybackService.STOP_ACTION);
context.sendBroadcast(intent);
}
}
}
}else if (action.equals(Intent.ACTION_SHUTDOWN)
|| action.equals("android.intent.action.ACTION_SHUTDOWN_IPO")) {
// When shutting down, saveQueue first then stop the player
saveQueue(true);
stop();
}
//Gionee <zhangjinbiao> <2017-01-18> modify for <64281> begin
else if (STOP_ACTION.equals(action)) {
nextsong();
//Gionee <zhangjinbiao> <2017-01-18> modify for <64281> end
}
else if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {
handleNextCmd();
} else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {
handlePrevCmd();
} else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {
handleTogglePauseCmd();
} else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {
handlePauseCmd();
} else if (CMDPLAY.equals(cmd) || PLAY_ACTION.equals(action)) {
play();
} else if (CMDSTOP.equals(cmd)) {
handlePauseCmd();
seek(0);
} else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
// Someone asked us to refresh a set of specific widgets,
// probably
// because they were just added.
int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
// <Gionee><zhangjc><2013-07-08> modify for CR00808250 begin
mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
// <Gionee><zhangjc><2013-07-08> modify for CR00808250 end
} else if (MediaSmallWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {
int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
mSmallWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);
} else if ("android.media.AUDIO_BECOMING_NOISY".equals(action)) {
Log.i(TAG, "ear control end mIsPlayerReady"+mIsPlayerReady);
//Gionee <liugp><2014-5-26> modify for CR01147194 begin
if(!mIsPlayerReady.get()){
mHeadSetPulloutWhenPreparingFlag = true;
}
//Gionee <liugp><2014-5-26> modify for CR01147194 end
pause(PauseReason.HEADSET_PULL_OUT);
//Gionee <liugp><2014-5-21> modify for CR01229604 begin
}else if(AppConsts.REFRESH_UPDATE_NOTIFICATION_ACTION.equals(action)){
updateNotification();
}else if (AppConsts.REFRESH_SONGINFO_ACTION.equals(action)) {
closeCursor();
String songId = String.valueOf(mPlayQueue.getCurSongId());
mCursor = CursorUtils.query(getApplicationContext(), Uri.parse("content://gnmusic/external/audio/media"),
mCursorCols, "_id=" + songId, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
notifyChange(PLAYSTATE_CHANGED);
}else if(AppConsts.SHAKE_PREF_CHANGE.equals(action)){
handleShakePrefChangeAction();
}
//Gionee <liugp><2014-5-21> modify for CR01229604 end
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
/****************************************************************************************************
过滤后同步PlayQueue
****************************************************************************************************/
/****************************************************************************************************
播放状态栏更新updateNowPlaying
****************************************************************************************************/
public static void updateNowPlaying(final Activity a, final boolean isBig) {
try {
if(a == null)
return;
//LogUtil.i(TAG, "enter updateNowPlaying");
if(a instanceof MusicBaseActivity) {
((MusicBaseActivity)a).refreshPlayingItem();
}
boolean isWhiteBg = false;
if(SkinMgr.getInstance().getThemeType() == 1 && !(a instanceof IdentifySongsActivity) && !(a instanceof IdentifyRecommendActivity)) {
isWhiteBg = true;
}
View bigView = a.findViewById(R.id.playbar_layout_big);
if(bigView == null) {
return;
}
bigView.setVisibility(View.GONE);
final ImageButton nextButton;
final ImageButton playButton;
ImageButton moreButton;
final AmigoProgressBar waitingProgressBar;
View playingView = a.findViewById(R.id.playbar_linearlayout);
TextView title;
TextView artist;
ImageView picIV = null;
bigView.setVisibility(View.VISIBLE);
nextButton = (ImageButton) a.findViewById(R.id.playbar_next);
playButton = (ImageButton) a.findViewById(R.id.playbar_play);
moreButton = (ImageButton) a.findViewById(R.id.playbar_more);
waitingProgressBar = (AmigoProgressBar) a.findViewById(R.id.playbar_pbar);
title = (TextView) a.findViewById(R.id.playbar_title);
artist = (TextView) a.findViewById(R.id.playbar_name);
picIV = (ImageView) a.findViewById(R.id.playbar_pic);
if(isWhiteBg) {
title.setTextColor(0xcc000000);
artist.setTextColor(0x80000000);
nextButton.setImageResource(R.drawable.icon_playbar_next_light);
playButton.setImageResource(R.drawable.icon_playbar_play_light);
moreButton.setImageResource(R.drawable.icon_more_menu_light);
}else {
title.setTextColor(0xccffffff);
artist.setTextColor(0x66ffffff);
nextButton.setImageResource(R.drawable.icon_playbar_next);
playButton.setImageResource(R.drawable.icon_playbar_play);
moreButton.setImageResource(R.drawable.icon_more_menu);
}
if (MusicUtils.sService != null) {
if (playingView != null) {
String artistName = PlayQueue.getInstance().getCurArtist();
String albumName = PlayQueue.getInstance().getCurAlbum();
if (artistName == null) {
artist.setVisibility(View.GONE);
} else {
artist.setVisibility(View.VISIBLE);
}
//Gionee <hongsq> <2016-12-15> modify for <40279> begin
if(picIV != null ) {
String path = CacheDirUtils.getAlbumFilePath(artistName,albumName);
BitmapDrawable drawable = null;
Drawable drawable2 = picIV.getBackground();
if(drawable2 != null && drawable2 instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable2).getBitmap();
if(bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
picIV.setBackground(null);
}
if (AppConfig.getInstance().getIsLocal() == false) {
if (path != null) {
drawable = ImageUtil
.getRoundBitmapDrawable(a, path);
if (drawable == null) {
drawable = ImageUtil.getRoundBitmapDrawable(a,
CacheDirUtils.getAlbumFilePath(
artistName, albumName));
}
}
}
else
{
if(path != null)
{
if(!FileUtil.isExist(path))
{
String albumsPath = MusicUtils.getLocalAlbums(a, PlayQueue.getInstance().getCurSongId());
if(null != albumsPath)
{
FileUtil.copyFile(albumsPath,path);
}
}
if(FileUtil.isExist(path))
{
drawable = ImageUtil
.getRoundBitmapDrawable(a, path);
if (drawable == null) {
drawable = ImageUtil.getRoundBitmapDrawable(a,
CacheDirUtils.getAlbumFilePath(
artistName, albumName));
}
}
}
}
//Gionee <hongsq> <2016-12-15> modify for <40279> end
if(drawable == null) {
if(isWhiteBg) {
drawable = ImageUtil.getBitmapDrawableById(a, R.drawable.icon_playbar_pic_skin_white);
}else {
drawable = ImageUtil.getBitmapDrawableById(a, R.drawable.icon_playbar_pic);
}
} else {
saveCurrentArtist(a, artistName);
}
picIV.setBackground(drawable);
}
title.setText(PlayQueue.getInstance().getCurSong());
if (MusicStore.UNKNOWN_STRING.equals(artistName)) {
artist.setText(R.string.unknown_artist_name);
} else {
artist.setText(artistName);
}
LogUtil.i(TAG, "updateNowPlaying sService.isPlaying()=" + sService.isPlaying());
if (sService.isPlaying()) {
if (PlayQueue.getInstance().getCurSongId() > 0) {
int playerState = sService.getPlayerState();
LogUtil.i(TAG, "updateNowPlaying playerState ="+playerState);
if (playerState == MediaPlaybackService.PAUSED_CANNOT_CONNECT_TO_SERVER
|| playerState == MediaPlaybackService.PREPRAING) {
waitingProgressBar.setVisibility(View.VISIBLE);
playButton.setVisibility(View.GONE);
} else {
if(isWhiteBg) {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_pause_light : R.drawable.icon_playbar_pause_small);
}else {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_pause : R.drawable.icon_playbar_pause_small);
}
waitingProgressBar.setVisibility(View.GONE);
playButton.setVisibility(View.VISIBLE);
}
}
} else {
if (PlayQueue.getInstance().getCurSongId() > 0 && PlayQueue.getInstance().getQueueSize() > 0) {
int playerState = sService.getPlayerState();
LogUtil.i(TAG, "updateNowPlaying 11 playerState ="+playerState);
if (playerState == MediaPlaybackService.PAUSED_CANNOT_CONNECT_TO_SERVER
|| playerState == MediaPlaybackService.PREPRAING) {
waitingProgressBar.setVisibility(View.VISIBLE);
playButton.setVisibility(View.GONE);
} else {
playButton.setVisibility(View.VISIBLE);
waitingProgressBar.setVisibility(View.GONE);
if(isWhiteBg) {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_play_light : R.drawable.icon_playbar_play_small);
}else {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_play : R.drawable.icon_playbar_play_small);
}
}
} else {
LogUtil.i(TAG, "updateNowPlaying 22");
playButton.setVisibility(View.VISIBLE);
waitingProgressBar.setVisibility(View.GONE);
if(isWhiteBg) {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_play_light : R.drawable.icon_playbar_play_small);
}else {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_play : R.drawable.icon_playbar_play_small);
}
}
}
}
} else {
if (playingView != null) {
if(isWhiteBg) {
playButton.setImageResource(R.drawable.icon_playbar_play_light);
}else {
playButton.setImageResource(R.drawable.icon_playbar_play);
}
waitingProgressBar.setVisibility(View.GONE);
playButton.setVisibility(View.VISIBLE);
}
}
playButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
if (sService != null) {
playButton.setEnabled(false);
if (sService.isPlaying()) {
sService.pause();
} else {
//在此调用播放方法,但不一定会播放成功,比如PlayQueue中没有歌曲
sService.play();
}
boolean isWhiteBg = false;
if(SkinMgr.getInstance().getThemeType() == 1 && !(a instanceof IdentifySongsActivity) && !(a instanceof IdentifyRecommendActivity)) {
isWhiteBg = true;
}
//判断调用了播放方法后,或者调用暂停方法后,是否处于正在播放状态,根据此时播放状态去设置播放状态的图标,
//而不是一点击就会换图标
if (sService.isPlaying()) {
playButton.setVisibility(View.VISIBLE);
if(isWhiteBg) {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_pause_light : R.drawable.icon_playbar_pause_small);
}else {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_pause : R.drawable.icon_playbar_pause_small);
}
} else {
playButton.setVisibility(View.VISIBLE);
if(isWhiteBg) {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_play_light : R.drawable.icon_playbar_play_small);
}else {
playButton.setImageResource(isBig ? R.drawable.icon_playbar_play : R.drawable.icon_playbar_play_small);
}
}
playButton.setEnabled(true);
}
} catch (Exception ex) {
LogUtil.i(TAG, ex.toString());
}
}
});
if(playingView != null){
playingView.setOnClickListener(nowPlayingEnterMediaPlayClickListener);
}
if(picIV != null){
picIV.setOnClickListener(nowPlayingEnterMediaPlayClickListener);
}
nextButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (sService == null) {
return;
}
nextButton.setEnabled(false);
MusicUtils.next(a, sService);
nextButton.setEnabled(true);
}
});
moreButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//createPopupMenu(a);
if (ClickUtils.isFastDoubleClick(1000)) {
return;
}
showPopupMenuDialog(a);
}
});
//LogUtil.i(TAG, "leave updateNowPlaying");
} catch (Throwable e) {
LogUtil.i(TAG, "ex : " + e.toString());
}
}
点击进入MediaPlaybackActivity的监听,会根据当前PlayQueue是否有歌曲而决定是否进去
private static OnClickListener nowPlayingEnterMediaPlayClickListener =
new OnClickListener() {
@Override
public void onClick(View v) {
LogUtil.i(TAG, "nowplaying enter mediaplaybackactivity");
Context context = v.getContext();
try {
if (sService.isOnlineMusic()) {
context.startActivity(new Intent(context, MediaPlaybackActivity.class)
.putExtra("isOnline", true));
} else {
if (MusicUtils.sService.getAudioId() != -1) {
context.startActivity(new Intent(context, MediaPlaybackActivity.class));
}
}
((Activity) context).overridePendingTransition(R.anim.media_play_back_in, R.anim.media_play_back_keep);
} catch (Exception e) {
// TODO: handle exception
}
}
};
播放状态栏只有这个方法会涉及到,就是说只有这个方法会更改这里的图标,绑定监听器,决定点击的逻辑.
/****************************************************************************************************
播放状态栏更新updateNowPlaying
****************************************************************************************************/
/****************************************************************************************************
文件扫描
1. 主要是MediaScanService,启动该Service。
2.在MediaScanService中的内部类ServiceHandler中扫描,该handler是在一个子线程中运行。因为Looper是在子线程创建的。运行时在MediaScanService中的run方法。
3.
String volume = arguments.getString("volume");
String[] directories = null;
if (MusicProvider.INTERNAL_VOLUME.equals(volume)) {
// scan internal media storage
directories = new String[] {Environment.getRootDirectory() + "/media"};
} else if (MusicProvider.EXTERNAL_VOLUME.equals(volume)) {
// scan external storage volumes
directories = mExternalStoragePaths;
} else {
// directories = volume;
}
if (directories != null) {
scan(directories, volume);
}
接下来主要是scan(directories, volume);
private void scan(String[] directories, String volumeName) {
LogUtil.i("scanTime", "begin scan in service --->");
// don't sleep while scanning
mWakeLock.acquire();
ContentValues values = new ContentValues();
values.put(MusicStore.MEDIA_SCANNER_VOLUME, volumeName);
LogUtil.i(TAG, "volumeName= " + volumeName);
Uri scanUri = getContentResolver().insert(Uri.parse("content://gnmusic/none/media_scanner"), values);
try {
if (volumeName.equals(MusicProvider.EXTERNAL_VOLUME)) {
openDatabase(volumeName);
}
LogUtil.i(TAG, "create MediaScaner begin");
sIsScanning.set(true);
LogUtil.i(TAG, "sIsScanning = " + sIsScanning);
GnMediaScanner scanner = createMediaScanner();
scanner.scanDirectories(directories, volumeName);
sIsScanning.set(false);
LogUtil.i(TAG, "sIsScanning++ = " + sIsScanning);
LogUtil.i(TAG, "create MediaScaner end");
} catch (Exception e) {
LogUtil.e(TAG, "exception in MediaScanner.scan()", e);
}
getContentResolver().delete(scanUri, null, null);
mWakeLock.release();
LogUtil.i("scanTime", "end scan in service --->");
}
看这个两句:
GnMediaScanner scanner = createMediaScanner();
scanner.scanDirectories(directories, volumeName);
(在Service#onCreate()中mExternalStoragePaths = storageManager.getVolumePaths();)
看看scanner.scanDirectories(directories, volumeName);
public void scanDirectories(String[] directories, String volumeName) {
LogUtil.i(TAG, "scanDirectories : " + "volumeName is: " + volumeName);
// mFilterSize = MusicPreference.getScanFilterSize(mContext);
mIsUpperLimitSet = MusicPreference.getUpperLimitCheck(mContext);
mMinFilterSize = MusicPreference.getMinFilterSize(mContext);
mMaxFilterSize = MusicPreference.getMaxFilterSize(mContext);
mFilteredFolders = (HashSet<String>) MusicPreference.getFilteredFolders(mContext);
if(mFilteredFolders == null){
mFilteredFolders = new HashSet<String>();
}
LogUtil.i(TAG, "mFilteredFolders = " + mFilteredFolders.toString());
try {
initialize(volumeName);
prescan(null, true);
if (directories.length <= 0) {
Log.d(TAG, "have no directories ");
noMusic = true;
return;
}
exitFlag.set(false);
asyncParseMusicFiles();
//递归扫码文件夹,并将匹配的文件放到allMusicFiles中让asyncParseMusicFiles()解析,把扫到的文件夹放到allMusicFolders中,在递归时候要用到
for (int i = 0; i < directories.length; i++) {
File f = new File(directories[i]);
scanMusicFiles(f);
}
if(android.os.Build.VERSION.SDK_INT >=23){
if(mFilteredFolders.contains(internal_music_dir) == false){
//单独扫描internal_music_dir,就是GIoneeDream所在文件夹
ArrayList<String> arrStr = getMusicFiles(internal_music_dir);
for(int i= arrStr.size() -1; i>=0; i--){
String s = arrStr.get(i);
//remove internal file if it not exist in MediaProvider,fix CR 1652433
if(MediaDBUtils.getInfoFromAudioDatabase(mContext, s).title == null){
arrStr.remove(i);
}
}
allMusicFiles.addAll(arrStr);
allMusicFolders.add(internal_music_dir);
}
}
exitFlag.set(true);
//等待asyncParseMusicFiles()结束
while(parseOverFlag.get() == false){
Thread.yield();
}
if(MediaScannerService.getCancelScanFlag() == false){
String []allowDir = new String[allMusicFolders.size()];
Iterator<String> it = allMusicFolders.iterator();
int i =0;
while(it.hasNext()){
allowDir[i++] = it.next();
}
//将没被过滤掉的文件夹中的所有歌曲的解析出来的东西写到库里面去。
postscan(allowDir);
LogUtil.i(TAG, "end scanDirectories");
//发送结束信号
sendFinishMsg(this.mCurScanCount);
}
} catch (Throwable e) {
Log.e(TAG, "Exception in GnMediaScanner.scan()", e);
e.printStackTrace();
}
}
在上面的方法中,比较关键的几句是:
//...... asyncParseMusicFiles(); for (int i = 0; i < directories.length; i++) { File f = new File(directories[i]); scanMusicFiles(f); } //......
while(parseOverFlag.get() == false){ Thread.yield(); }
if(MediaScannerService.getCancelScanFlag() == false){ String []allowDir = new String[allMusicFolders.size()]; Iterator<String> it = allMusicFolders.iterator(); int i =0; while(it.hasNext()){ allowDir[i++] = it.next(); } postscan(allowDir); LogUtil.i(TAG, "end scanDirectories"); sendFinishMsg(this.mCurScanCount);
//...... 首先介绍下asyncParseMusicFiles();是mAudioUri = Uri.parse("content://gnmusic/external/audio/media");private void asyncParseMusicFiles(){ new Thread( new Runnable(){ @Override public void run() { // TODO Auto-generated method stub parseOverFlag.set(false); LogUtil.i(TAG, "enter asyncParseMusicFiles"); //当exitFlag.get() == false时,size为0也会继续,因为allMusicFiles是一个LinkedBlockingQueue<String>,当没有元素时,会阻塞线程等待。 while(allMusicFiles.size() >0 || exitFlag.get() == false){ if(MediaScannerService.getCancelScanFlag() == true) break ; String s = allMusicFiles.poll(); if(s != null){ File f = new File(s); //该方法负责扫描一个文件会在GNMediaScanner#doScanFile()中分别调用beginFile()和endFile() //把结果(就是歌曲的各种信息和路径信息)放在ArrayList<ContentValues> GNMediaScanner#cvlist中 fileScan(s, f.lastModified() / 1000, f.length()); } } parseOverFlag.set(true); LogUtil.i(TAG, "exit asyncParseMusicFiles"); } }).start(); }
mFilesUri = Uri.parse("content://gnmusic/external/file");
而mPath是directories的中的string
asyncParseMusicFiles();就是异步(就是跟文件扫描不是一个线程,而文件扫描和主线程又不是一个线程,所以是三个线程)解析LinkedBlockingQueue allMusicFile中的file。
在postScan()结束后,就发送扫描结束信号。由MusicScanActivity的静态成员sScanProgressHandler处理。ScanProgressHandler extends Handler。主要调用updateUIWhenFinishScan(msg);
/****************************************************************************************************
文件扫描
****************************************************************************************************/
/****************************************************************************************************
reloadQueue
****************************************************************************************************/ reloadQueue();
/****************************************************************************************************
reloadQueue
****************************************************************************************************