/** * * @author king * * 查找类接口 */ public class ClassUtils { public static List<Class> getAllClassByInterface(Class c) {//按照接口字母顺序查找 List<Class> returnClassList = new ArrayList<Class>(); if (c.isInterface()) { String packageName = c.getPackage().getName(); try { try { List<Class> allClass = getClasses(packageName); for (int i = 0, len = allClass.size(); i < len; i++) { if (c.isAssignableFrom(allClass.get(i))) { if (!c.equals(allClass.get(i))) { returnClassList.add(allClass.get(i)); } } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return returnClassList; } private static List<Class> getClasses(String packageName) throws IOException, ClassNotFoundException { ClassLoader classLoader = Thread.currentThread() .getContextClassLoader(); String path = packageName.replace('.', '/'); Enumeration<URL> resources = classLoader.getResources(path); List<File> dirs = new ArrayList<File>(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); dirs.add(new File(resource.getFile())); } ArrayList<Class> classes = new ArrayList<Class>(); for (File directory : dirs) { classes.addAll(findClasses(directory, packageName)); } return classes; } private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException { List<Class> classes = new ArrayList<Class>(); if (!directory.exists()) { return classes; } File[] files = directory.listFiles(); for (File file : files) { if (file.isDirectory()) { assert !file.getName().contains("."); classes.addAll(findClasses(file, packageName + "." + file.getName())); } else if (file.getName().endsWith(".class")) { classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6))); } } return classes; } }
------- android培训、java培训、期待与您交流! ----------
前几天完成了android的MP3项目,大部分功能都成功解决了,虽然简单,但是很多问题都通过自己查询资料解决了,很有成就感,于是对其中的感悟做一些总结性的工作。
里面我觉得非常具有高难度的工作当在播放歌曲的时候,歌词一行一行的作相应的变换,以及当我拖动播放音乐的进度条的时候,歌曲和歌词都会跳到相应的地方。
让我贴出源代码一步一步的分析(当然这只是整个mp3项目里面的一小部分)
我们首先看,当我点击一音乐的时候,会打开mp3播放的activity以及MP3播放mp3的service。
先说MP3播放的service,因为我们播放的歌词的时候,是希望即使我们不停留在其中mp3播放的activity的时候也是希望能够顺利听到音乐的,这就是要求了我们要将播放mp3这个功能做成一个service组件,service组件的特点就是在后台播放,与 与用户交流的界面无关,且当系统资源不过分不足的时候,是不会轻易关闭的。现在我们来看
其中mp3播放的service里面的全部代码:
package com.music.player; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Queue; import java.util.Set; import com.music.list.AllMusicActivity; import com.music.lrc.LrcProcessor; import com.music.main.MainActivity; import com.music.mp3.Lrc; import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.util.Log; public class PlayerService extends Service{ //播放器对象 public static MediaPlayer player=null; //代表是否正在播放 public static boolean isplaying=false; //代表是否处于停止状态 public static boolean stop=false; //各种操作代号 public static final int PLAY=0; //播放 public static final int PAUSE=1; //暂停 public static final int STOP=2; //停止 public static final int LAST=3; //上一曲 public static final int NEXT=4; //下一曲 public static final int MOVE=5; //拖动滚动条播放 public static final int CREATE=-1; //创建播放器对象 //歌词对象 Lrc lrc=null; //这是播放时间的分和秒 int min=0,currentMin=0; int sec=0,currentSec=0; //更新时间与进度条线程 Handler handler=new Handler(); //这是从QueueTime里面得到时间 long nextTimeMill=0; //歌词内容 String lrcContent=null; //用于存储lrc歌词的时间 public static Queue<Long> timeQueue=null; //用于存储lrc歌词的时间和内容 public static LinkedHashMap<Long,String> map=null; //系统开始播放时间 long begin=0; //当前播放了多少时间 long mv=0; //集合中存放的键 Set<Long> s=null; //判断是否按下暂停 boolean putpause=true; //暂停时系统时间 long startime=0; //播放时系统喜欢 long endtime=0; //暂停了多少时间 long pausetime=0; //播放模式 public static int pattern=0; //是否显示歌词 public static int look=0; @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } //启动Service进行的操作,由上一个activity发送过来的intent启动下面的这句话 @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub //获得操作类型 int doPlayer=intent.getIntExtra("doPlayer", 0); if(doPlayer==CREATE){ createPlayer(); }else if(doPlayer==PLAY){ play(); }else if(doPlayer==PAUSE){ pauseMp3(); }else if(doPlayer==STOP){ stopMp3(); }else if(doPlayer==NEXT){ changeMp3(); }else if(doPlayer==LAST){ changeMp3(); }else if(doPlayer==MOVE){ move(); } return super.onStartCommand(intent, flags, startId); } //播放操作 public void play(){ if(player!=null){//什么时候当播放对象不等空时 if(stop){//当stop为true时。这两种情况结合起来分析就是因为有些时候我们在播放的时候点击了stop。 createPlayer(); stop=false; isplaying=true; }else{ //获得暂停了多少时间.这一段是因为暂停了之后再点击play而写的。因为这都是为歌词显示做的工作,所以lrc文件不为空 if(lrc!=null){ if(!putpause){ endtime=System.currentTimeMillis(); pausetime+=endtime-startime; handler.post(updateSeekBar); putpause=true; } } player.start();//播放音乐 isplaying=true; } } } //暂停操作 public void pauseMp3(){ if(player!=null){ if(lrc!=null){ //获得按下暂停时的系统时间 if(putpause){ startime=System.currentTimeMillis(); handler.removeCallbacks(updateSeekBar); putpause=false;//这样就不能再点暂停键了 } } player.pause(); isplaying=false; } } //停止操作 public void stopMp3(){ if(player!=null){ if(!stop){ Mp3PlayerActivity.time.setText(0+""+0+":"+0+""+0); timeQueue=null; mv=0; pausetime=0; Mp3PlayerActivity.movetime=0; handler.removeCallbacks(updateSeekBar); Mp3PlayerActivity.bar.setProgress(0); Mp3PlayerActivity.lr.setText(""); player.stop(); player.release(); isplaying=false; stop=true; } } } //创建播放歌曲 public void createPlayer(){ //这一段中的Uri.parse("file://"+Mp3PlayerActivity.path)应该是一个String的对象转换为一个uri的对象 player=MediaPlayer.create(this,Uri.parse("file://"+Mp3PlayerActivity.path));//拿到player对象 if(look==0){//等于0表示看歌词 lrc=getLrc();//得到歌词对象 if(lrc!=null){ readLrc(); }else{ Mp3PlayerActivity.lr.setText("未搜索到歌词!"); } }else if(look==1){//等于1表示不看歌词 Mp3PlayerActivity.lr.setText(""); } Mp3PlayerActivity.name.setText("歌曲:"+Mp3PlayerActivity.m.getMp3_name());//设置相应的位置显示歌曲 //为mediaPlayer设置是否播放完成的监听器。歌曲播放完之后,我们要进行的处理都写在了new CompletionListener()这一代码中,因为涉及循环播放、单曲播放之类的 player.setOnCompletionListener(new CompletionListener()); // 设置默认进度条的最大值setMax和getduration 都是 系统自带的方法,看其名知其意 Mp3PlayerActivity.bar.setMax(player.getDuration()); //这里是以为毫秒为单位 //启动handler handler.post(updateSeekBar);//每个Handler的实例都关联了一个线程和线程的消息队列。当创建了一个Handler对象时,一个线程或消息队列同时也被创建,该Handler对象将发送和处理这些消息或Runnable对象。 //在这里updateSeekBar是一个runnable对象,所以当调用post方法后,updateSeekBar的run()方法即将开始执行 player.start(); begin=System.currentTimeMillis();//Returns the current system time in milliseconds isplaying=true; stop=false; } //歌曲切换时操作 public void changeMp3(){ if(stop){ createPlayer(); }else{ stopMp3(); createPlayer(); } } Runnable updateSeekBar=new Runnable(){ public void run() { //这个mv时间设置的非常巧妙。是不算暂停时间(减去了pausetime)和去除了拖动滚动条的时间(减去了movetime)。是现在时间和开始时间之差 mv=System.currentTimeMillis()-begin+Mp3PlayerActivity.movetime-pausetime; int CurrentPosition = 0;// 设置默认进度条当前位置 //得到mediaPlayer的当前播放位置 CurrentPosition = player.getCurrentPosition();//这个是一个int类型 //设置进度条的当前位置..真的觉得android的设计师已经为我们设计的非常好了。就等着我们自己好好的使用 Mp3PlayerActivity.bar.setProgress(CurrentPosition); min=(player.getDuration()/1000)/60; sec=(player.getDuration()/1000)%60; currentMin=(CurrentPosition/1000)/60; currentSec=(CurrentPosition/1000)%60; //为时间Text设置时间 if(currentSec<10){//这里是为了显示时间,当小于10的时候只有一位,但是我们为了好看还是设置了2位,怎么设置就是前面加了一个0 Mp3PlayerActivity.time.setText(0+""+currentMin+":"+"0"+currentSec); }else{ Mp3PlayerActivity.time.setText(0+""+currentMin+":"+currentSec); } //设置歌词 if(look==0){ if(lrc!=null){ if(mv>=nextTimeMill)//当这个播放的时间大于了歌词显示时间,表示就要显示下一个时间点的歌词了 { if(timeQueue.peek()!=null)//注意体会peek和poll的区别peek只是取出,不会删除。但是poll会删除。这里只是判断有没有下一个时间点 { Mp3PlayerActivity.lr.setText(lrcContent);//设置歌词显示的内容 nextTimeMill=timeQueue.poll();//这里还要是取出并删除。 lrcContent=map.get(nextTimeMill);//根据时间取出显示的内容 }else{ lrcContent=""; Mp3PlayerActivity.lr.setText(lrcContent); } } } } handler.postDelayed(updateSeekBar,10); //这里表示每隔10毫秒,updateSeekBar的run()方法就会执行一次,注意这是一个单独的线程 } }; //监听是否播放完成 class CompletionListener implements OnCompletionListener{ @Override public void onCompletion(MediaPlayer mp) { // TODO Auto-generated method stub Mp3PlayerActivity.time.setText(0+""+0+":"+0+""+0); timeQueue=null; mv=0; pausetime=0; Mp3PlayerActivity.movetime=0; handler.removeCallbacks(updateSeekBar);//这里可以理解为从解除handler与线程updateSeekbar区别。那么从此以后handler不再这个结束了updateseekbar相关联了,不再会调用run方法里面的postdelay方法了 Mp3PlayerActivity.bar.setProgress(0); Mp3PlayerActivity.lr.setText(""); if(pattern==0)//这个应该是单曲循环,因为index没有改变 { createPlayer(); }else if(pattern==1){//这个是下一首,分两种情况 if(Mp3PlayerActivity.index==AllMusicActivity.mp3list.size()-1)//这就是说明index已经到达文件的最末端 { Mp3PlayerActivity.index=0; Mp3PlayerActivity.m=AllMusicActivity.mp3list.get(Mp3PlayerActivity.index); Mp3PlayerActivity.path=MainActivity.SDPath+"mp3/"+Mp3PlayerActivity.m.getMp3_name()+".mp3"; createPlayer(); }else{ Mp3PlayerActivity.index=Mp3PlayerActivity.index+1; Log.i("index", Mp3PlayerActivity.index+"");// //在 控制台输出日志....和log.w()代表warn,log.e()代表error,log.v()代表verbose,log.d()代表debug一样,都是用来输出日志,只是标记不一样。 Mp3PlayerActivity.m=AllMusicActivity.mp3list.get(Mp3PlayerActivity.index); Mp3PlayerActivity.path=MainActivity.SDPath+"mp3/"+Mp3PlayerActivity.m.getMp3_name()+".mp3"; createPlayer(); } }else if(pattern==2){ int i=(int)(Math.random()*AllMusicActivity.mp3list.size()); Mp3PlayerActivity.index=i; Mp3PlayerActivity.m=AllMusicActivity.mp3list.get(Mp3PlayerActivity.index); Mp3PlayerActivity.path=MainActivity.SDPath+"mp3/"+Mp3PlayerActivity.m.getMp3_name()+".mp3"; createPlayer(); } } } //拖动滚动条时操作 public void move(){ if(look==0){ if(lrc!=null){//如果lrc不为空,表示需要对lrc文件进行处理。 //这个movetime的计算是Mp3PlayerActvity那个函数里面 //这个mv时间设置的非常巧妙。是不算暂停时间(减去了pausetime)和去除了拖动滚动条的时间(减去了movetime)。是现在时间和开始时间之差 mv=System.currentTimeMillis()-begin+Mp3PlayerActivity.movetime-pausetime; //下句只是为了看看到底这几个数字是怎么样的 System.out.println(System.currentTimeMillis()+" "+begin+" "+Mp3PlayerActivity.movetime); timeQueue=new LinkedList<Long>(); for(Long l:s){//是从s里不断的取出值,然后赋值给l,让l进行下面的操作 if(l>=mv){//mv就是当前播放时间,只需要将大于的当前播放的时间的l取出来,与mv相比,大于的话,则把l放进timequeue里面去。这样timequeue里面就放好了我们需要的时间点 timeQueue.offer(l); // l <— the element to add } } // readLrc();我觉得这一句不应该要,要了就乱了啊 nextTimeMill=timeQueue.poll(); lrcContent=map.get(nextTimeMill); Mp3PlayerActivity.lr.setText(lrcContent); player.seekTo((int)mv);//注意这是mediaplayer的一个方法 }else{ player.seekTo(Mp3PlayerActivity.i);//这里就是拉动滚动跳后直接松开的地方 } }else if(look==1){ player.seekTo(Mp3PlayerActivity.i); } } public void readLrc(){ map=lrc.getContent(); timeQueue=new LinkedList<Long>();//这里是建立了一个队列 s=map.keySet();//将map中的键(键其实是歌词的时间点)作为一个集合赋值给了s for(Long l:s){ timeQueue.offer(l);//将s里面的键一个一个的从头到尾的加入到队列中 } nextTimeMill=timeQueue.poll();//return:the head of this queue。从队列从从头至尾一个一个取出时间点 lrcContent=map.get(nextTimeMill);//用键(即时间点)获得歌词内容 Mp3PlayerActivity.lr.setText(lrcContent);//再将不同时间点的歌词内容给显示出来 } //获得Lrc文件对象 public Lrc getLrc(){//原来去掉mp3的后缀是为了更方便得到lrc的文件 String path=MainActivity.SDPath+"mp3/"+Mp3PlayerActivity.m.getMp3_name()+".lrc"; InputStream inputStream=null; Lrc lrc_result=null; try { inputStream = new FileInputStream(path);//这个函数应该就是给了其需要打开的文件的路径,就可以打开这个路径形成是输入流 LrcProcessor p=new LrcProcessor(); lrc_result=p.process(inputStream); } catch (FileNotFoundException e) { // TODO Auto-generated catch block return null; } return lrc_result; } }
分析流程,首先看到onStartCommand(Intent intent, int flags, int startId) ,这里其实我们只是用了第一个参数的内容,这是因为在mp3的播放界面的时候点击了相应的按钮后,会随着intent传送一个int类型的数字过来,当然不同的数字都有不同的含义。上面的定义已经给出了。
关于createPlayer():
我们先看在正常播放的时候,当然我们在上一个歌曲列表的界面点击某首歌的时候,将会打开这个service,并且在intent的里面含有的doPlayer==CREATE。
所以会引发createPlayer()的方法。其中如果要看歌词的话,会这样lrc=getLrc()得到歌词对象,再调用readLrc(),其中map=lrc.getContent(),
是
public LinkedHashMap<Long, String> getContent() {
return content;
}
将这个链表的hashmap赋值给map。
其他的内容,请参看readLrc()函数里面的注释,我写的很清楚了。
接着,createPlayer()里还有一段非常重要,这一段设置
Mp3PlayerActivity.bar.setMax(player.getDuration()); //这里是以为毫秒为单位
其中getDuration () 的方法:
public int getDuration ()
Added in API level 1
Gets the duration of the file.
Returns
the duration in milliseconds
得到的是一个以毫秒为单位的数字,这一点记录开始时间的begin的单位想匹配,这样为我们切换拉动进度跳的时候找到相应的歌词提供了非常大的方便。
begin=System.currentTimeMillis();//Returns the current system time in milliseconds
其中,是为了慢慢的滚动歌词而单独设置的线程
handler.post(updateSeekBar)。这一段我也写了详尽的注释。
其它的play();pauseMp3();stopMp3();changeMp3();changeMp3();比较简单,请自己参看,。
我最想特别说明的是:move(),即当我们在播放的MP3的activity里面拖动了进度条的时候,
相应代码:
bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { //这里的几个方法,我们只关心,用户在哪里按下和在哪里松开按钮,所以我们只写了一个onStopTrackingTouch和onStartTrackingTouch public void onStopTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub if(!PlayerService.stop){//如果音乐器正在播放 i=bar.getProgress();//得到松开手的进度条的位置 movetime+=(i-startmove);//得到两者的差值 player_intent.putExtra("doPlayer",PlayerService.MOVE); startService(player_intent); }else{ bar.setProgress(0); } } @Override public void onStartTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub startmove=bar.getProgress();//当用户想要拖动进度条的时候,第一次触碰的地方 } @Override public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) { // TODO Auto-generated method stub } });
就会调用move()。这里面对拖动的时间的处置利用这样一个算式:
//这个mv时间设置的非常巧妙。是不算暂停时间(减去了pausetime)和去除了拖动滚动条的时间(减去了movetime)。是现在时间和开始时间之差
mv=System.currentTimeMillis()-begin+Mp3PlayerActivity.movetime-pausetime;
再清空时间点的队列:timeQueue=new LinkedList<Long>();
再把只是比mv大的部分的时间点加入到timeQueue队列即可,再让handler.post(updateSeekBar)这个线程里run()去处理拖动后的歌词。这样歌词的显示就正常了。
我们再看看,对歌曲播放的的处理,居然只有一句话,
player.seekTo(Mp3PlayerActivity.i)
这个i=bar.getProgress();(来自bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener()的onStopTrackingTouch(SeekBar arg0) (松开手指的地方) )
而public synchronized int getProgress ()
Returns:
the current progress, between 0 and getMax()
还记得我们是如何设置这个bar的最大值的吗?就是
Mp3PlayerActivity.bar.setMax(player.getDuration());
这样再去设置歌曲的时候只需要拿到松开手处的bar.getProgress(),再seekto到bar.getProgress()即可。
虽然好像我再纠结非常2的问题。但是我觉得让我思路变得好清晰好清晰。
------- android培训、java培训、期待与您交流! ----------
所谓进程通信,就是不同进程之间进行一些"接触",这种接触有简单,也有复杂。机制不同,复杂度也不一样。通信是一个广义上的意义,不仅仅指传递一些massege。
1. 消息传递(管道,FIFO,posix和system v消息队列)
2. 同步(互斥锁,条件变量,读写锁,文件和记录锁,Posix和System V信号灯)
3. 共享内存区(匿名共享内存区,有名Posix共享内存区,有名System V共享内存区)
4. 过程调用(Solaris门,Sun RPC)
消息队列和过程调用往往单独使用,也就是说它们通常提供了自己的同步机制.相反,共享内存区通常需要由应用程序提供的某种同步形式才能 正常工作.解决某个特定问题应使用哪种IPC不存在简单的判定,应该逐渐熟悉各种IPC形式提供的机制,然后根据特定应用的要求比较它们的特性.
必 须考虑的四个前提:
1. 联网的还是非联网的.IPC适用于单台主机上的进程或线程间的.如果应用程序有可能分布到多台主机上,那就要考虑使用套接字代替IPC,从而简化以后向联 网的应用程序转移的工作.
2. 可移植性.
3. 性能,在具体的开发环境下运行测试程序,比较几种IPC的性能差异.
4. 实时调度.如果需要这一特性,而且所用的系统也支持posix实时调度选项,那就考虑使用Posix的消息传递和同步函数.
各种 IPC之间的一些主要差异:
1. 管道和FIFO是字节流,没有消息边界.Posix消息和System V消息则有从发送者向接受者维护的记录边界(eg:TCP是没有记录边界的字节流,UDP则提供具有记录边界的消息).
2. 当有一个消息放置到一个空队列中时,Posix消息队列可向一个进程发送一个信号,或者启动一个新的线程.System V则不提供类似的通知形式.
3. 管道和FIFO的数据字节是先进先出的.Posix消息和System V消息具有由发送者赋予的优先级.从一个Posix消息队列读出时,首先返回的总是优先级最高的消息.从一个System V消息队列读出时,读出者可以要求想要的任意优先级的消息.
4. 在众多的消息传递技术—管道,FIFO,Posix消息队列和System V消息队列—中,可从一个信号处理程序中调用的函数只有read和write(适用于管道和FIFO).
比较不同形式的消息传递时,我 们感兴趣的有两种测量尺度:
1. 带宽(bandwidth):数据通过IPC通道转移的速度.为测量该值,我们从一个进程向另一个进程发送大量数据(几百万字节).我们还给不同大小的 I/O操作(例如管道和FIFO的write和read操作)测量该值,期待发现带宽随每个I/O操作的数据量的增长而增长的规律.
2. 延迟(latency):一个小的IPC消息从一个进程到令一个进程再返回来所花的时间.我们测量的是只有一个1个字节的消息从一个进程到令一个进程再回 来的时间(往返时间)
在现实世界中,带宽告诉我们大块数据通过一个IPC通道发送出去需花多长时间,然而IPC也用于传递小的控制信 息,系统处理这些小消息所需的时间就由延迟提供.这两个数都很重要.