音乐模块--首页--本地歌曲

NativeSongActivity


首先是一个viewpage,使用的是public static class MyViewpagerFragment extends ViewPageFragment

public abstract class ViewPageFragment extends Fragment;

而ViewPageFragment持有一个MusicViewPager对象。

通过MyViewpagerFragment#initFragmentList()中初始化了ViewPage需要的fragment。将需要显示的页面的fragment加入MyViewpagerFragment的mFragments中。

还需要在MyViewpagerFragment.getTagText()返回所有viewpage对应的tab字符串。

在MyViewpagerFragment#initFragmentList()中加入了三个fragment。分别是AllSongFragment,SingerFragment,AlbunFragment。

AllSongFragment是NativeSongActivity的内部类。

class AllSongFragment extends NativeListFragment;

class NativeListFragment extends BaseListFragment;

class BaseListFragment extends Fragment;

其中BaseListFragment持有一个protected GnMusicDragListView mListView;是一个ExpandableListView。必然会有一个adapter。

该ListView在右边缘首先会把事件传给子view,如果move事件就接下来的事件由listview自己处理。和listview同一个frameLayout的是AmigoAlphabetIndexView;AmigoAlphabetIndexView覆盖在listview上面。覆盖了整个listview,而不只是看起来的只有一竖条。。

BaseListFragment使用的是tracklist.xml。AmigoAlphabetIndexView的显示和停止由AmigoAlphabetIndexView的onTouchListener和listview的onTouchListener共同完成,

还有其他地方,如切换page时,在void doOnPageChange(int pageNo)中控制。还有在nativesongactivity的onPause()中控制。


SingerFragment和AlbumFragment差不多,我就主要说说SingerFragment。

SingerFragment没有像AllSongFragemtn有那么复杂的继承关系,直接继承Fragment。

下面细看SingerFragment的成员:

AsyncQueryHandler mQueryHandler;    用于查询数据库返回一个Cursor。具体查询的参数等在SingerFragment#getArtistCursor(AsyncQueryHandler async, String filter)中。

Cursor mArtistCursor;     用于保存mQueryHandler返回的cursor。在listView和adapter直接使用这个cursor,而不是用list作为dataset。

ArrayList<String> mArtistList;     用于保存本地没有对应artist的图片的那些Aritist name。本来应该是在联网时用来下载图片的,但是海外是本地版,所以没有下载功能,用到mArtistlist的代码已经裁剪了,但是这个变量还保留着。

boolean mIsBulkOp     用于判断当前是否已经处于批量选择状态了。

List<Integer> mBulkList       用于存储被选中的Artist的id。

View mNoSongZone;    没有歌曲时的页面显示的View。

Bitmap mDefaultBitmap;     Artist没有对应图片时使用的默认图片。

LruCache<String, Bitmap> mMemoryCache;       图片缓存工具。缓存artist的圆形图片。缓存在内存中。


关于批量选择过程:

长按就会出发批量选择。

然后会隐藏某些view,显示出某些view,list变得不可点击,并出现了原来gone的小方框。

在每个MyViewpageFragment中的fragment的onLongClickListenter中都会调用NativeSongActivity的doBulkClick(int pos)。在批量选择模式下弹出的收藏、删除、全部选择按钮都是属于NativeSongActivity的,点击这些按钮的话,会调用各自fragment的一些方法(方法名是一样的分别是add和delete开头的方法)。但各个fragment并没有同一个并没有继承同一个接口,这个是不合理的。所以fragment和NativeSongActivity是存在交互的。


在Artist和album这两个页面跳进去分别是SingerActivity和AlbumActivity,都差不多的。

需要传几个参数给Singer Activity,artistName,artistId,分别用于显示、获取对应图片和获取对应该artistId的歌曲。

下面介绍其成员:

SingerPicFragment mFragmentDown; 用于显示上部的图片。

NativeSongListFragment mFragmentUp;    用于显示歌曲列表。

GradientRelativeLayout mBaseLayout;       用于显示虚化背景。  SingerActivity是继承GnMusicLayeredPageActivity。但是虚化背景的layout是在MusicBaseActivity中加入的,就是在musicbaselayout.xml中。虚化背景的设置在SingerActivity.initBlurBg()中。 主要使用了BlurBitmapUtils.getBlurBitmap(bmp1, new BlurBitmapParam(180.0f, 180.0f, 2));将一个普通图片处理了一下。


刪除音乐逻辑

首先startActivityForResult( intent ),启动DeleteItems这个Activity,然后调用DeleteItems#doDelelteItems()方法.

public static void deleteTracks(Context context, long[] list) {
        LogUtil.d(TAG, ">> deleteTracks");
        String[] cols = new String[] {MusicStore.Audio.Media._ID, MusicStore.Audio.Media.DATA,
                MusicStore.Audio.Media.ALBUM_ID};
        StringBuilder where = new StringBuilder();
        where.append(MusicStore.Audio.Media._ID + " IN (");
        for (int i = 0; i < list.length; i++) {
            where.append(list[i]);
            if (i < list.length - 1) {
                where.append(",");
            }
        }
        where.append(")");
        // Make sure database exist when deleting and make sure we have latest
        // news
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return;
        }
        Cursor c = CursorUtils.query(context, MusicStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, where.toString(), null,
                null);

        if (c != null) {

            // step 1: remove selected tracks from the current playlist, as well
            // as from the album art cache
            try {
                c.moveToFirst();
                while (!c.isAfterLast()) {
                    // remove from current playlist
                    long id = c.getLong(0);
                    sService.removeTrack(id, 1);
                    c.moveToNext();
                }
            } catch (Throwable ex) {
                 Log.i(TAG, "deleteTracks e  =" + ex);
            }

            // step 2: remove selected tracks from the database
            if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                return;
            }
            try {
                context.getContentResolver().delete(MusicStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        where.toString(), null);
            } catch (Exception e) {
                LogUtil.d(TAG, "Exception", e);
                // Just in case
                return;
            }

            // step 3: remove files from card
            c.moveToFirst();
            while (!c.isAfterLast()
                    && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String name = c.getString(1);
                File f = new File(name);
                try { // File.delete can throw a security exception
                    if(name.startsWith("/system/media/gn_resources/music")){
                        deleteFileInDB(name,context.getContentResolver());
                    }
                    if (!f.delete()) {
                        // I'm not sure if we'd ever get here (deletion would
                        // have to fail, but no exception thrown)
                        LogUtil.e(TAG, "Failed to delete file " + name);
                    }
                    c.moveToNext();
                } catch (SecurityException ex) {
                    c.moveToNext();
                }
            }

            // step 4: remove info from recognize table and backup table
            try {
                c.moveToFirst();
                ArrayList<String> paths = new ArrayList<String>();
                while (!c.isAfterLast()) {
                    String path = c.getString(1);
                    if (!TextUtils.isEmpty(path)) {
                        paths.add(path);
                    }
                    c.moveToNext();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            c.close();
        }

        // We deleted a number of tracks, which could affect any number of
        // things
        // in the media content domain, so update everything.
        context.getContentResolver().notifyChange(Uri.parse("content://gnmusic"), null);
        Bundle args = new Bundle();
        args.putString("volume", MusicProvider.EXTERNAL_VOLUME);
        Intent intent = new Intent();
        intent.setClassName("com.android.providers.media", "com.android.providers.media.MediaScannerService");
        intent.putExtras(args);
        try {
            context.startService(intent);
        } catch (Exception e) {
            LogUtil.d(TAG, "Exception", e);
        }
        
        long minSize = MusicPreference.getMinFilterSize(context);
        int n2 = MediaDBUtils.getMusicFileCount(context,minSize);
        MusicPreference.setMediaFileCnt(context,n2);
        LogUtil.d(TAG, "<< deleteTracks");
    }

在这个方法里面,做了五件事:

1.查询数据库时候有mItemList中打songID

2.从PlayQueue中的playList中移除对应的元素,使用PlayQueue#remove(int)

3.从数据库中移除这些uri

4.从存储器中移除这些文件

5.删除对应的备份信息

这个方法并没有在删除后,调用next()或者通知metachange,playqueueChange等信息.

调用完DeleteItems#doDelelteItems()方法后,就会远程调用了MediaPlaybackService的private final IBinder mBinder = new ServiceStub(this)的gotoNextSong()

static class ServiceStub extends IMediaPlaybackService.Stub

public void gotoNextSong() {
            if (mService != null && mService.get() != null) {
                mService.get().next(true);
            }
        }

最终调用了MediaPlaybackService#next(boolean).

public void next(boolean force) {
        synchronized (this) {
            LogUtil.d(TAG, ">> next(" + force + ")");
            if (mOnlineMusic) {
                if (!NetworkUtil.isNetworkConnected(this)) {
                    LogUtil.d(TAG, "Network disConnected isPlaying()="+isPlaying());
                    if (isPlaying()) {
                        pause(PauseReason.SWICH_SONG);
                    }
                    return;
                }
            }
            mPlayQueue.next(isRandPlayMode());
          //Gionee <zhangjinbiao> <2017-02-27> modify for <74429> begin
            if(0 == mPlayQueue.getQueueSize())
            {
            	LogUtil.i(TAG, "0 == mPlayQueue.getQueueSize");
            	stop(true);
                if (!mOnlineMusic) {
//                    play();
                    notifyChange(META_CHANGED);
                }
            	return;
            }
          //Gionee <zhangjinbiao> <2017-02-27> modify for <74429> begin
            stop(false);
            openCurrent(true);
            // Gionee <liugp><2013-05-22> modify for CR00823332 begin
            Intent intent = new Intent("com.android.music.play.next");
            sendBroadcast(intent);
            // Gionee <liugp><2013-05-22> modify for CR00823332 end
            if (!mOnlineMusic) {
                play();
                notifyChange(META_CHANGED);
            } else {
                notifyChange(REFRESH_BAR);
                updateNotification();
            }
        }
    }

看看 mPlayQueue.next(isRandPlayMode());这一句:

public void next(boolean randomNext){
        if(playList == null)
            return;
        if(randomNext == true){
            curPos = getRandomPos();
        }else{
            if (curPos < playList.size() - 1) {
                // 如果不相等,应该是之前做过删除操作
                if (getCurSongId() == playList.get(curPos).getSongId()) {
                    curPos++;
                }
            } else {
                curPos = 0;
            }
        }
        setCurPlayItem(curPos);
        LogUtil.i(TAG, "playQueue next()   " + "playList size:  " + playList.size() + "curPos: " +   curPos );
    }
好像curPos怎么都不是小于0的.

然后需要在setCurPlayItem()中做处理,

private void setCurPlayItem(int pos){
        if(playList != null && playList.size() > 0){
            if(pos >= 0 && pos < playList.size()){
                curPos = pos;
                LogUtil.i(TAG, "setCurPlayItem pos="+curPos);
                curPlayItem = playList.get(curPos);
            }            
        }
        //Gionee <zhangjinbiao> <2017-2-27> modify for <74429> begin
        else {
        	curPlayItem = null;
        }
        //Gionee <zhangjinbiao> <2017-2-27> modify for <74429> end
    }
在PlayList.size()==0的时候,就让curPlayItem为null,因为其他地方一般会直接操作这个curPlayItem,而不是curPos,如果curPlayItem为null,就会让他们知道没有歌曲.

然后在看看MediaPlaybackService中的这几句

 if(0 == mPlayQueue.getQueueSize())
            {
            	LogUtil.i(TAG, "0 == mPlayQueue.getQueueSize");
//用于停掉音乐和取消Service作为前台和让通知栏中打音乐消失
                stop(true);
                if (!mOnlineMusic) {
//                    play();
           //通知更新
                     notifyChange(META_CHANGED);
                }
            	return;
            }

MusicBaseActivity中有这个监听器

f.addAction(MediaPlaybackService.META_CHANGED);
        registerReceiver(mPlayStatusListener, filter);

private BroadcastReceiver mPlayStatusListener = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            LogUtil.i(TAG,"mPlayStatusListener mIspaused="+mIspaused);
            if(mIspaused == false){
                MusicUtils.updateNowPlaying(MusicBaseActivity.this);
            }
        }
    };
updataNowPlaying的代码如下:



就是更新下面那个播放状态栏.

META_CHANGE还会通知到大小Widget,即桌面RemoteView,可以看到,如果curPlayItem为null的话,那么intent中打extra都会为null,因为getArtistName()会

通过curPlayItem去拿.

void notifyChange(String what) {
        LogUtil.d(TAG, "notifyChange(" + what + ")");
        try {
            Intent i = new Intent(what);
            i.putExtra("id", Long.valueOf(getAudioId()));
            i.putExtra("artist", getArtistName());
            i.putExtra("album", getAlbumName());
            i.putExtra("track", getTrackName());
            i.putExtra("playing", isPlaying());
            //Gionee <hongsq> <2015-12-17> adapt for <offline version> begin
            i.putExtra("oversea",true);
            //Gionee <hongsq> <2015-12-17> adapt for <offline version> end
            if (QUIT_PLAYBACK.equals(what)) {
                // for QUIT_PLAYBACK, do NOT use sticky broadcast
                sendBroadcast(i);
            } else {
                sendStickyBroadcast(i);
            }
            if (what.equals(QUEUE_CHANGED)) {
                saveQueue(true);
            } else {
                saveQueue(false);
            }
            // Share this notification directly with our widgets
            mAppWidgetProvider.notifyChange(this, what);
            mSmallWidgetProvider.notifyChange(this, what);
        } catch (Throwable e) {
            // TODO: handle exception
        }

    }


更新查询

在每次进入NativeSongActivity页面,都会重新刷新页面,就是调用各个fragment的刷新方法.就是在NativeSongActivity#onResume( )中

public void reInitTrackCursorEx() {
            if (mAllSongFg != null) {
                mAllSongFg.reInitTrackCursor();
            }

            if (mSingerFg != null) {
                mSingerFg.reInitTrackCursor();
            }
            
            if(mAlbumFg != null){
            	mAlbumFg.reInitTrackCursor();
            }
        }
对于mAllSongFg的话,就是调用NativeListFragment#initTrackCursor( )
protected void initTrackCursor() {
        try {
            closeCursor();
            
            /*if(mSortOrder == null){
                if (mCurrentSourceLocation == null || (!mCurrentSourceLocation
                    .equals("all") && !mCurrentSourceLocation
                    .equals("featured"))) {
                    mSortOrder = "title_key";
                }
            }*/
            
            if (mBestCollectionsId != null) {     
                //从我喜欢或这自建歌单进入
                if(mBestCollectionsId.equals("-1")) {
                    //我喜欢歌单
                    mBestCollectionsId = "" + MusicDBUtils.idForplaylist(mContext, "my_featured");
                }else if(mBestCollectionsId.equals("-2")){
                    //我收藏的歌单
                    mBestCollectionsId = "" + getPlaylistIdByPlaylistName(mSelectedName);
                }
                Uri queryUri = Uri.parse("content://gnmusic/external/audio/playlists/" + mBestCollectionsId
                        + "/members");
                mTrackQueryHandler.startQuery(0, null, queryUri, mBestCollectionCursorCols, mWhere.toString(),
                        null, mSortOrder);

            } else {
                //从扫描页面进入
                Uri queryUri = Uri.parse("content://gnmusic/external/audio/media");
                mTrackQueryHandler
                        .startQuery(0, null, queryUri, mCursorCols, mWhere.toString(), null, mSortOrder);
            }
            //Log.d("yangfeng", "nativelist mSortOrder == " + mSortOrder);
        } catch (Exception e) {
            e.printStackTrace();
            Log.i("NativeListFragment", "mCurrentSourceLocation = " + mCurrentSourceLocation);
            /*Log.i("NativeListFragment", "mCursorCols = " + mCursorCols.toString() + ", where = " + mWhere
                                 + ", mSortOrder = " + mSortOrder);*/
            Log.i("NativeListFragment", e.toString());
        }
    }


专辑图片及艺术家图片获取:在MusicUtils中的getLocalAlbums(Context c, long songId),在内存中会有两份图片,只是一张图片,

两个命名而已,但是都是从ALBUM_ARTIST来的

在数据库查询而得到的图片,会缓存到Android/data/com.android.music中.而数据库本身解析的歌曲图片会放在

Android/data/com.android.provider.media/albumthumbs中.

图片获取的逻辑大概是,先看本地有没有缓存,没有就去查数据库.

 public static String getLocalAlbums(Context c, long songId) {
    	String songPath = null;
    	String[] mCursorCols = new String[] {
                MusicStore.Audio.Media.ARTIST, MusicStore.Audio.Media.ALBUM, MusicStore.Audio.Media.TITLE,
                MusicStore.Audio.Media.DATA};
    	Cursor mCursor = CursorUtils.query(c, MusicStore.Audio.Media.EXTERNAL_CONTENT_URI,
                mCursorCols, "_id=" + songId, null, null);
        if (mCursor != null) {
        	if(mCursor.getCount() > 0){
	            mCursor.moveToFirst();
	            songPath = mCursor.getString(3);
	            Log.e("getAlbums", "1:songPath:"+songPath);
	            mCursor.close();  
        	} else {
        		mCursor.close();
        		return null;
        	}
        } else {
        	Log.e("getAlbums", "not find songPath!!!");
        	return null;
        }
    	
    	
        String[] idCols = new String[] {
                MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.ALBUM_ID,MediaStore.Audio.Media.DATA
        };
        
        String album=null;
        Cursor idCur = null;
        idCur = CursorUtils.query(c, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, idCols, MediaStore.Audio.Media.DATA+"=?", new String[]{songPath}, null);
        if (idCur == null) {
        	Log.e("getAlbums", "NOT FIND ALBUM ID!");
        	return null;
        }
        
        if (idCur.getCount() > 0) {
	        	Log.e("getAlbums", "11.COUNT:"+idCur.getCount());
	        	idCur.moveToFirst();
	            Log.e("getAlbums", "2:ALBUM:"+idCur.getString(0)+",ALBUM_ID:"+idCur.getLong(1));
	            album = idCur.getString(0);
	            idCur.close();           
        } else {
        	idCur.close();
        	return null;
        }

        Uri uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
        String[] cols = new String[] {
                MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ALBUM_ART
        };
        Cursor cur = null;        
        cur = CursorUtils.query(c,    
                MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, cols, MediaStore.Audio.Albums.ALBUM+"=?", new String[]{album}, null); 
        if (cur == null) {
        	Log.e("getAlbums", "CUR IS NULL");
            return null;
        }
        String path = null;
        Log.e("getAlbums", "cur.getCount():"+cur.getCount());
        if (cur.getCount() > 0) {
        	cur.moveToFirst();
            path = cur.getString(1);
            Log.e("getAlbums", "3:ALBUM:"+cur.getString(0)+",ALBUM_ART:"+cur.getString(1));
            cur.close();
            return path;
        } else {
            cur.close();
            return null;
        }
    }














  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值