zoukankan      html  css  js  c++  java
  • 2018-2019-2 本地音乐播放器cocoMusic 代码分析

    代码组成部分

    app:

    • manifests
      • AndroidManifest.xml
    • java
      • com.example.honl(androidTest)
      • com.example.honl.muiscoco
        • BaseActivity 抽象类,绑定/解绑Service
        • bottomInfoFr 点击“更多选项”中的“歌曲信息”弹出的片段
        • cocoPlayerAPP Application
        • Constant 一些常量
        • LocalMusicFragment 最近播放和我的收藏歌单Fragment
        • MainActivity 主活动
        • Mp3Info 歌曲类
        • MusicUtils 连接媒体库
        • MyMusicListAdapter musiclist适配器
        • MyMusicListFragment 显示本地音乐播放列表+下方播放条的片段
        • PlayActivity 播放页
        • PlayServive 播放服务
        • SplashActivity 欢迎页
      • com.example.honl.muiscoco(test)
    • res
      • anim
        • dialog_fr_in.xml 点击“更多选项”中的“歌曲信息”弹出的片段的动画效果
        • dialog_fr_out.xml 点击“更多选项”中的“歌曲信息”片段消失的动画效果
      • drawble
        • ic_launcher_background.xml
        • ic_launcher_foreground.xml
      • layout
        • activity_main.xml 主活动的布局
        • activity_play.xml 播放页的布局
        • activity_splash.xml 欢迎页的布局
        • fragment_bottom_info.xml 点击“更多选项”中的“歌曲信息”片段的布局
        • fragment_my_music_list.xml 本地全部歌曲播放列表+下方播放条的布局
        • item_music_list.xml 播放列表中的每个item的布局
        • local.xml “最近播放”和“我的收藏”播放列表片段的布局
      • mipmap 图片资源
      • values
        • colors.xml
        • dimens.xml
        • strings.xml
        • styles.xml

    代码调用关系

    • 在SplashActivity中点击“跳过”按钮,或等待3秒后,将自动跳转至MainActivity。
    • MyMusicListFragment与LocalMusicFragment均在onAttach中关联到MainActivity。
    • MyMusicListFragment中设置适配MyMusicListAdapter。
    • PlayService中提供.play方法给其他类调用来播放歌曲。
    • MainActivity关联activity_main布局。
    • PlayActivity关联activity_play布局。
    • bottomInfoFr关联fragment_bottom_info布局。
    • SplashActivity关联activitiy_splash布局。
    • LocalMusicFragment关联fragment_local_music布局。
    • MyMusicListFragment关联fragment_my_music_list布局。

    核心代码分析

    MediaPlayer介绍

    音乐播放器使用的媒体类是内置的媒体类
    MediaPlayer介绍
    下面代码是使用cursor用于从数据库中查询歌曲的信息,保存在List当中

       public static ArrayList<Mp3Info> getMp3Infos(Context context) {
        System.out.println("MediaUtils.java #2 : " + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
        Cursor cursor = context.getContentResolver().query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
                MediaStore.Audio.Media.DURATION + ">=10000", null,
                MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
    
        ArrayList<Mp3Info> mp3Infos = new ArrayList<Mp3Info>();
        System.out.println("MediaUtils.java #3 :cursor.getCount() : " + cursor.getCount());
        for (int i = 0; i < cursor.getCount(); i++) {
            cursor.moveToNext();
            Mp3Info mp3Info = new Mp3Info();
            long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID));//音乐id
            String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));//音乐标题
            String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));//艺术家
            String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));//专辑
            long albumid = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));//专辑id
            long duration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));//时长
            long size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));//文件大小
            String url = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));//文件路径
            int isMusic = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));//是否为音乐
    
            if (isMusic != 0) {
                mp3Info.setId(id);
                mp3Info.setTitle(title);
                mp3Info.setArtist(artist);
                mp3Info.setAlbum(album);
                mp3Info.setAlbumId(albumid);
                mp3Info.setDuration(duration);
                mp3Info.setSize(size);
                mp3Info.setUrl(url);
                mp3Infos.add(mp3Info);
                System.out.println("MediaUtils.java #401 : title = " + title + " | artist = " + artist + " | duration = " + duration);
                System.out.println("MediaUtils.java #402 : id = " + id + " | album = " + album + " | size = " + size);
                System.out.println("MediaUtils.java #403 : url = " + url);
                System.out.println("MediaUtils.java #404 : mp3Infos = " + mp3Infos.size());
                System.out.println("MediaUtils.java #405 : mp3islove = " + mp3Info.getIsLove());
    
            }
        }
        cursor.close();
        System.out.println("MediaUtils.java #405 : mp3Infos = " + mp3Infos.size());
        return mp3Infos;
    }
    

    通过数据库中albumId来获得媒体封面

    public static Bitmap loadCoverFromMediaStore(Context context,long albumId) {
        ContentResolver resolver =  context.getContentResolver();
        Uri albumUri = ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumId);
        InputStream is;
        try {
            is = resolver.openInputStream(albumUri);
        } catch (FileNotFoundException ignored) {
            return null;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        return BitmapFactory.decodeStream(is, null, options);
    }
    

    基础

    因为播放器切换音乐时候涉及不断切换后台服务和前端UI,BaseActivity和PlayService是音乐播放器的基础,之中提供和实现了更新UI和更新音乐信息的接口。

    BaseActivity

    在BaseActivity中提供两个抽象类来用来更新音乐,UI

    public abstract void publish(int progress);
    public abstract void change(int progress);
    

    在BaseActivity基础之上,建立了PlayActivity和MainActivity类。
    音乐的播放信息,比如播放位置、播放模式、是否收藏都需要活动与服务之间通信。

     //绑定service
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            PlayService.PlayBinder playBinder = (PlayService.PlayBinder) service;
            playService = playBinder.getPlayService();
            playService.setMusicUpdateListener(musicUpdateListener);
            musicUpdateListener.onChange(playService.getCurrentPosition());
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
            playService = null;
            isBound = false;
    
        }
    };
    //绑定服务
    public void bindPlayService() {
        if (isBound == false) {
            Intent bindintent = new Intent(this, PlayService.class);
            bindService(bindintent, conn, Context.BIND_AUTO_CREATE);//绑定服务
            System.out.println("我已经绑定");
            isBound = true;
        }
    
    }
    //解绑服务
    public void unbindPlayService() {
        if (isBound == true) {
            unbindService(conn);
            System.out.println("松绑了");
            isBound = false;
        }
    }
    

    PlayService

    在PlayService中提供一个接口用来更新音乐,UI

    public interface MusicUpdateListener {
    public void onPublish(int progress);
    
    public void onChange(int position);
    }
    

    数据库

    在cocoPlayerAPP类中,创建数据库:

    public static DbUtils dbUtils;
    dbUtils = DbUtils.create(getApplicationContext(),Constant.DB_NAME);   
    

    之后在MyMusicListFragment、PlayActivity和LocalMusicFragment中通过save保存播放记录,通过update更新数据。

    在Help->Find Action->搜索Device File Explorer->/data/data/<包名>/databases/下可以找到cocoPlayerDB.db

    自己实现的功能分析

    SharedPreferences

    当活动与服务绑定后,也就是在活动中触发音乐信息的变化时可以与服务同步;但是在每一次打开软件的时候,歌曲信息都是初始化界面。Android提供了一种方式可以记录用户信息,

    public class cocoPlayerAPP extends Application {
    public static SharedPreferences sp;
    
    public static DbUtils dbUtils;
    
    public static Context context;
    public void onCreate() {
    
        super.onCreate();
    
        sp = getSharedPreferences(Constant.SP_NAME, Context.MODE_PRIVATE);
    
        //保存用户设置:播放模式、歌曲位置,进度值等;
        // MainActivity 的onDestroy()保存状态值
        //在PlayService的Oncreate恢复状态
        dbUtils = DbUtils.create(getApplicationContext(),Constant.DB_NAME);
        context = getApplicationContext();
    }
    }
    

    然后在活动或服务中就可以如下代码获取之前存储的音乐信息(音乐位置,播放模式)

    currentPosition = app.sp.getInt("currentPosition", 0);
    play_mode = app.sp.getInt("play_mode", PlayService.ORDER_PLAY);
    

    更新时间

    播放歌曲的时候,需要将歌曲时间与seekbar进度条绑定。也就是绑定UI。
    static class MyHandler extends Handler {
    private PlayActivity playActivity;

        public MyHandler(PlayActivity playActivity) {
            this.playActivity = playActivity;
        }
    
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (playActivity != null) {
                switch (msg.what) {
                    case UPDATE_TIME://更新时间(已经播放时间)
                        playActivity.textView1_start_time.setText(MusicUtils.formatTime(msg.arg1));
                        break;
                }
            }
        }
    }
    

    更新PlayService中的音乐信息:

     Runnable updateSteatusRunnable = new Runnable() {
        @Override
        public void run() {
            //不断更新进度值
            while (true) {
                if (musicUpdateListener != null && mPlayer != null && mPlayer.isPlaying()) {
    				//实现BaseActivity的接口
                    musicUpdateListener.onPublish(getCurrentProgress());//获取当前的进度值
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    //
    private ExecutorService es = Executors.newSingleThreadExecutor();
    //在oncreate添加下面语句,即可
     es.execute(updateSteatusRunnable);
    

    fragment滑动

    在MainActivity中有一个滑动切换的效果,通过FragmentPagerAdapter来实现。(需要建立多个fragment)

    public class MyPagerAdapter extends FragmentPagerAdapter {
    
        private final String[] Titles = {"本地音乐", "我的歌单"};
    
        public MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }
    
        public CharSequence getPageTitle(int position) {
            return Titles[position];
        }
    
        @Override
        public Fragment getItem(int position) {
            if (position == 0) {
                //System.out.println("哦吼左边");
                if (myMusicListFragment == null) {
                    myMusicListFragment = MyMusicListFragment.newInstance();
                }
                return myMusicListFragment;
            } else if (position == 1) {
                //System.out.println("哦吼右边");
                System.out.println("MainActivity.MyPagerAdapter.position=1");
                if (localMusicFragment == null) {
                    localMusicFragment = LocalMusicFragment.newInstance();
                }
                return localMusicFragment;
            }
            return null;
        }
    
        @Override
        public int getCount() {
            return Titles.length;
        }
    }
    

    监听listview中Item内部控件

    1. 在Adapter中创建一个listener
      private final View.OnClickListener listener ;
    2. 设置控件监听
      vh.imageView4_more.setOnClickListener(listener);
      vh.imageView4_more.setTag(position);
    3. 在fragment中onClick就可以直接使用case来匹配

    不同fragemnt的同步

    不同fragemnt的同步其实是一个很棘手的问题。因为涉及到不同的活动或者fragment,同步的时候需要考虑之前fragment或Activity的信息。
    fragment部分:
    在onCreateView中,绑定服务。
    在onResume中,绑定服务。
    在onPause中,解绑服务。
    在onDestroyView中,解绑服务。
    Activity部分,把播放服务的绑定和解绑放在onResume,onPause里,好处是,每次回到当前Activity就获取一次播放状态:
    在oncreate中,绑定SharedPreferences。
    在onResume中,绑定服务。
    在onPause中,解绑服务。
    在onDestroyView中,解绑服务。

  • 相关阅读:
    dotnet core 获取 MacAddress 地址方法
    dotnet core 获取 MacAddress 地址方法
    dotnet core 发布只带必要的依赖文件
    dotnet core 发布只带必要的依赖文件
    Developing Universal Windows Apps 开发UWA应用 问答
    Developing Universal Windows Apps 开发UWA应用 问答
    cmd 如何跨驱动器移动文件夹
    cmd 如何跨驱动器移动文件夹
    C++ 驱动开发 error LNK2019
    C++ 驱动开发 error LNK2019
  • 原文地址:https://www.cnblogs.com/Shambryce/p/10890013.html
Copyright © 2011-2022 走看看