原创内容,转载请注明出处

1、介绍

学习Android已经有一段时间了,但是都是一些零零散散的知识点,还需要能够将这些知识点串起来,以便加深对Android的了解。下面将通过一个小项目来将最近所学的知识串起来,在该项目中会涉及到Activity、Service、BroadCast Recevier三大组件;还有ListActivity、TabActivity;使用Android的MediaPlayer类播放音频文件;Android的多线程类HandlerThread和Handler处理类使用;Java文件下载、输入输出流的使用;SAX基于事件驱动解析XML文件;Properties文件的处理;Tomcat服务的简单搭建等知识点。

2、分析

首先该Mp3播放器的主要功能如下:Mp3文件播放暂停,Mp3文件下载,本地Mp3文件展示,服务器Mp3文件展示。首先用户进入Mp3播放器,查看目前服务器上有哪些Mp3歌曲,将需要的Mp3文件下载下来,然后在本地文件列表中播放Mp3歌曲。

功能分析

1、在该项目中主要有四个界面:服务器Mp3歌曲文件列表,本地歌曲文件列表,设置界面,关于界面。

2、服务器歌曲文件列表页面,包含获取服务器Mp3文件列表、下载服务器Mp3文件、列表更新。

3、本地歌曲列表,包含本地Mp3文件展示、歌曲文件播放暂停等操作、列表更新。

4、设置界面,提供系统的通用设置给用户,像本地文件路径等。

5、关于界面,展示系统介绍。

6、考虑到操作方便,将服务器文件列表和本地文件列表合并在一个切换标签页中展示。

3、项目主要说明

3.1、使用Tomcat搭建mp3 Web工程

1.在tomcat目录下的webapps中新建文件夹mp3,即创建mp3简单应用工程。

2.在mp3下创建WEB-INF文件夹,并在WEB-INF文件夹下创建web.xml文件,此时完成mp3 web工程的搭建。

<?xml version="1.0" encoding="ISO-8859-1"?><web-app xmlns="http://java.sun.com/xml/ns/javaee"   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"   version="2.5">   </web-app>

3.进入doc命令,启动tomcat服务。

4.在mp3目录下添加mp3歌曲和配置文件resources.xml。resources.xml文件代表服务器上所有文件列表信息,由Android客户端来获取

<?xml version="1.0" encoding="utf-8"?><resources><resource><id>00001</id><mp3Name>chunni.mp3</mp3Name><mp3Size>4122018</mp3Size><mp3Path>http://192.168.2.115:8080/mp3</mp3Path></resource><resource><id>00002</id><mp3Name>17sui.mp3</mp3Name><mp3Size>3856845</mp3Size><mp3Path>http://192.168.2.115:8080/mp3</mp3Path></resource><resource><id>00003</id><mp3Name>jintian.mp3</mp3Name><mp3Size>3623184</mp3Size><mp3Path>http://192.168.2.115:8080/mp3</mp3Path></resource></resources>


Android实战——Mp3播放器_第1张图片

3.2、构建服务器文件列表页面

1.使用eclipse开发工具创建Mp3RemoteActivity类和对应布局文件activity_mp3_remote.xml。由于使用ListView控件,这里我使用自定义的ListView内容布局list.xml。

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:orientation="horizontal" >    <TextView        android:id="@+id/mp3Name"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentLeft="true" />    <TextView        android:id="@+id/mp3Size"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentRight="true" />    <TextView        android:id="@+id/mp3Path"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:visibility="invisible" /></RelativeLayout>

2.修改activity_mp3_remote.xml布局文件,增加ListView控件,id值设为系统自带andorid.R.id.list。

<ListView        android:id="@android:id/list"        android:layout_width="fill_parent"        android:layout_height="wrap_content" />

3.修改Mp3RemoteActivity类,让它去继承ListActivity类。重写onCreate方法,首先在该方法中注册自定义下载广播接收器,然后在该读取服务器文件列表,并更新ListView控件的数据。

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_mp3_remote);IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(Mp3Contant.downloadaction);//注册下载文件广播接收registerReceiver(new DownloadReceiver(), intentFilter);//更新ListView数据update();}

4.更新MP3列表使用多线程去获取服务器上的数据,因为该过程可能会由于网络等原因导致Activity无响应,影响用户的体验度。

/** * 更新MP3列表 *  * @author Alan * @time 2015-7-21 下午3:53:24 */private void update(){//创建线程HandlerThread handlerThread = new HandlerThread("updateUI");handlerThread.start();//创建HandlerHandler handler = new Handler(handlerThread.getLooper());//将Runnable加到Handler队列中handler.post(runnable);}

获取服务器文件列表后,并更新ListView控件的数据。由于在Android的UI是线程不安全,故而在Android4.0版本及以上规定,更新UI不能通过其他线程来更新,只能调用主线程更新UI。对于更新UI的问题,Android提供了多种处理方式,第一Activity类中有一个runOnUiThread()方法,该方法中的内容将在UI线程执行,故而可将UI更新的方法放置在该方法中;第二种是使用多线程HandlerThread和Handler处理类的回调函数处理;第三种是通过广播机制也可实现,目前只了解过这三种方式,亲测都ok,本案例将使用runOnUiThread方法来完成。

@Overridepublic void run() {//获取下载内容String content = DownloadUtil.readFile(Mp3Contant.serverDefaultMp3Path,Mp3Contant.serverDefaultResourceName);//将下载内容包装成InputSource对象InputSource inputSource = new InputSource(new StringReader(content));//解析MP3内容列表List<Mp3> mp3s = XParse.parse(inputSource);//创建ListAdapter适配器adapter = convertListAdapterByMp3s(mp3s);//更新UI处理必须在UI线程上runOnUiThread(new Runnable() {@Overridepublic void run() {// 更新ListView的ListAdapter适配器Mp3RemoteActivity.this.setListAdapter(adapter);}});}};

还有下载文件代码和解析Xml文件的代码未列出这些都属于Java Se部分的内容,具体可看源代码。

5.重写onListItemClick方法(listview点击事件),在该方法中使用多线程来下载服务器上的mp3文件。

/** * listview点击事件 * @param l * @param v * @param position * @param id * @author Alan * @time 2015-7-21 下午4:11:54 */@Overrideprotected void onListItemClick(ListView l, View v, int position, long id) {super.onListItemClick(l, v, position, id);HashMap<String, String> hashMap = (HashMap<String, String>) l.getItemAtPosition(position);String mp3Name = hashMap.get("mp3Name");//获取MP3名称String mp3Path = hashMap.get("mp3Path");//获取MP3路径//Mp3下载采用多线程进行HandlerThread thread  = new HandlerThread("downloadFile");thread.start();Handler handler = new Handler(thread.getLooper());//开始处理Mp3文件下载handler.post(new MyRunnable(mp3Path, mp3Name));}

下载文件成功后,通过Android的广播机制,发布广播,在页面上提示下载成功。

class MyRunnable implements Runnable{private String mp3Name;private String mp3Path;public MyRunnable(String mp3Path,String mp3Name){this.mp3Path = mp3Path;this.mp3Name = mp3Name;}@Overridepublic void run() {//下载文件DownloadUtil.downloadFile(mp3Path, mp3Name,Mp3Contant.defaultMp3Path,mp3Name);//发送广播Intent intent = new Intent();intent.setAction(Mp3Contant.downloadaction);intent.putExtra("msg", mp3Name+"下载成功");sendBroadcast(intent);}}

以下是广播接收器的代码,主要是提示最终下载结果的消息。

        @Overridepublic void onReceive(Context context, Intent intent) {Bundle bundle = intent.getExtras();String msg = (String) bundle.get("msg");//提示消息Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();}

6.重写onCreateOptionsMenu方法去添加菜单按钮,并重写onOptionsItemSelected方法去处理菜单按钮的具体监听事件。

@Overridepublic boolean onCreateOptionsMenu(Menu menu) {//添加设置按钮menu.add(1,SETTING,1,R.string.setting);//添加关于按钮menu.add(1,ABOUT,2,R.string.about);//添加更新按钮menu.add(1,UPDATE,3,R.string.update);return super.onCreateOptionsMenu(menu);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {int id = item.getItemId();if (id == UPDATE) {//更新列表update();}else if(id == SETTING){//进入设置界面Intent intent = new Intent();intent.setClass(this, SettingActivity.class);startActivity(intent);}else if(id == ABOUT){//进入关于界面Intent intent = new Intent();intent.setClass(this, AboutActivity.class);startActivity(intent);}return super.onOptionsItemSelected(item);}

3.3、构建本地文件列表展示页面

1.创建Mp3LocalActivity类和对应的布局文件activity_mp3_local.xml。

2.修改布局文件内容,添加ListView控件,并添加播放、上一首、下一首三个按钮,具体布局如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.cygoat.mp3.activity.Mp3LocalActivity" >    <LinearLayout        android:id="@+id/linearlayout"        android:layout_width="fill_parent"        android:layout_height="wrap_content" >        <ListView            android:id="@android:id/list"            android:layout_width="fill_parent"            android:layout_height="wrap_content" />    </LinearLayout>    <RelativeLayout        android:layout_width="fill_parent"        android:layout_height="wrap_content">        <Button            android:id="@+id/prev"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="@string/prev" />        <Button            android:id="@+id/play"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:text="@string/start"/>        <Button            android:id="@+id/next"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentRight="true"            android:text="@string/next" />    </RelativeLayout></LinearLayout>

3.修改Mp3LocalActivity类,重写onCreate方法,在该方法中为按钮注册监听器,并更新ListView数据。

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_mp3_local);prev = (Button) findViewById(R.id.prev);play = (Button) findViewById(R.id.play);next = (Button) findViewById(R.id.next);//添加监听器prev.setOnClickListener(this);play.setOnClickListener(this);next.setOnClickListener(this);//更新ListView数据update();}

更新ListView数据主要从本地Mp3目录中搜索mp3文件,然后展示。

/** * 更新MP3列表 *  * @author Alan * @time 2015-7-21 下午3:53:24 */private void update(){//获取Properties对象Properties properties = PropertiesUtil.getProperties(Mp3Contant.settingFileName);//获取MP3路径String mp3Path = properties.getProperty(Mp3Contant.CharContant.mp3Path);//获取mp3文件File[] files = getMp3Files(mp3Path);//将MP3文件转换ListAdapterListAdapter adapter = convertListAdapterByFiles(files);//为Activity设置adapterthis.setListAdapter(adapter);}

4.考虑到当从播放器回到主页时,播放器的Activity处于不可见状态,该Activity可能会被Java垃圾回收器回收(在Android优先级顺序由高到低:处于可见的Activity>Service>不可见Activity)。因此为了避免播放音乐资源被垃圾回收器回收,导致播放中断,故而使用Service组件来完成音乐播放。

音乐播放操作事件主要在点击播放、上一首、下一首按钮,和点击ListView中的数据项触发。

重写onListItemClick方法,在该方法中实现音乐播放的操作。代码如下

/** * listview点击事件 * @param l * @param v * @param position * @param id * @author Alan * @time 2015-7-21 下午4:11:54 */@Overrideprotected void onListItemClick(ListView l, View v, int position, long id) {super.onListItemClick(l, v, position, id);//创建跳转Intent实例Intent intent = new Intent();//将文件名称设置到Intent实例中intent.putExtra("fileName", getFullFileName(position));if(currPosition != position){intent.putExtra("isNew", true);}else{intent.putExtra("isNew", false);}intent.setClass(this, PlayService.class);//开启mp3播放服务startService(intent);//设置当前播放mp3歌曲位置currPosition = position;}

5.创建播放音乐服务PlayService类,在该类中实现音乐播放功能处理。处理音乐播放主要使用MediaPlayer类,由于该类使用比较简单,具体查看源代码。PlayService代码如下

package com.cygoat.mp3.service;import android.app.Service;import android.content.Intent;import android.media.MediaPlayer;import android.os.Bundle;import android.os.IBinder;public class PlayService extends Service {private MediaPlayer mediaPlayer;//当前播放状态private static final int INIT_STATUS = 0;//初始化状态private static final int ACTIVE_STATUS = 1;//播放状态private static final int PAUSE_STATUS = 2;//暂停状态private static final int STOP_STATUS = 3;//停止状态//播放命令public static final int START_COMMAND = 1;//启动命令public static final int PAUSE_COMMAND = 2;//暂停命令public static final int STOP_COMMAND = 3;//停止命令private int playStatus = INIT_STATUS;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();mediaPlayer = new MediaPlayer();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Bundle bundle = intent.getExtras();//取出文件名称变量和是否是切换歌曲播放变量String fileName = (String) bundle.get("fileName");boolean isNew = (Boolean) bundle.get("isNew");Integer command = (Integer) bundle.get("command");//播放play(fileName, isNew,command);return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();}private void play(String fileName , boolean isNew , Integer command){//如果是切换新的歌曲,则直接播放if(isNew){active(fileName);return;}//同一首歌之间状态切换if((playStatus == INIT_STATUS || playStatus == STOP_STATUS) && ((command==null?START_COMMAND:command) ==START_COMMAND)){//当前歌曲是初始化和停止状态,如果此时下发播放命令,则开始播放active(fileName);return;}if(playStatus == ACTIVE_STATUS && ((command==null?PAUSE_COMMAND:command)==PAUSE_COMMAND)){//当前歌曲是播放状态,如果此时下发暂停命令,则暂停播放pause();return;}if(playStatus == PAUSE_STATUS && ((command==null?START_COMMAND:command)==START_COMMAND)){//当前歌曲是暂停状态,如果此时下发播放命令,则继续播放reActive();return;}if((playStatus == ACTIVE_STATUS || playStatus == PAUSE_STATUS) && ((command==null?STOP_COMMAND:command) ==STOP_COMMAND)){//当前歌曲状态是播放和暂停,如果此时下发停止命令,则停止播放stop();return;}}/** * 播放歌曲 * @param fileName * @author Alan * @time 2015-7-21 下午7:31:24 */private void active(String fileName){try{mediaPlayer.reset();mediaPlayer.setDataSource(fileName);mediaPlayer.prepare();mediaPlayer.start();playStatus = ACTIVE_STATUS;}catch(Exception e){e.printStackTrace();}}/** * 暂停播放 *  * @author Alan * @time 2015-7-21 下午7:31:37 */private void pause(){mediaPlayer.pause();playStatus = PAUSE_STATUS;}/** * 继续播放 *  * @author Alan * @time 2015-7-21 下午7:31:49 */private void reActive(){mediaPlayer.start();playStatus = ACTIVE_STATUS;}/** * 停止播放 *  * @author Alan * @time 2015-7-21 下午8:20:49 */private void stop(){mediaPlayer.stop();playStatus = STOP_STATUS;}}

6.重写onCreateOptionsMenu方法去添加菜单按钮,并重写onOptionsItemSelected方法去处理菜单按钮的具体监听事件。

@Overridepublic boolean onCreateOptionsMenu(Menu menu) {//添加设置按钮menu.add(1,SETTING,1,R.string.setting);//添加关于按钮menu.add(1,ABOUT,2,R.string.about);//添加更新按钮menu.add(1,UPDATE,3,R.string.update);return super.onCreateOptionsMenu(menu);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {int id = item.getItemId();if (id == UPDATE) {//更新列表update();}else if(id == SETTING){//进入设置界面Intent intent = new Intent();intent.setClass(this, SettingActivity.class);startActivity(intent);}else if(id == ABOUT){//进入关于界面Intent intent = new Intent();intent.setClass(this, AboutActivity.class);startActivity(intent);}return super.onOptionsItemSelected(item);}

3.4、构建切换标签主界面

1.创建MainActivity和对应布局文件activity_main.xml。

2.修改activity_main.xml布局文件,由于是使用TabActivity,该文件必须以TabHost作为根节点,根节点下必须包括TabWidget和FrameLayout。

<?xml version="1.0" encoding="utf-8"?><TabHost xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@android:id/tabhost"    android:layout_width="fill_parent"    android:layout_height="fill_parent" >    <LinearLayout        android:layout_width="fill_parent"        android:layout_height="fill_parent"        android:orientation="vertical"        android:padding="5dp" >        <TabWidget            android:id="@android:id/tabs"            android:layout_width="fill_parent"            android:layout_height="wrap_content" />        <FrameLayout            android:id="@android:id/tabcontent"            android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:padding="5dp" />    </LinearLayout></TabHost>

3.修改MainActivity继承TabActivity 。重写onCreate方法中将Mp3RemoteActivity和Mp3LocalActivity界面添加到主界面上的Tab切换页签上。

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获取tabHost,即整个TabTabHost tabHost = getTabHost();//创建Intent跳转对象Intent intent = new Intent();intent.setClass(this, Mp3LocalActivity.class);//新建一个标签页TabHost.TabSpec spec = tabHost.newTabSpec("mp3LocalActivity");//设置标签名称,还可设置图片spec.setIndicator("本地");//设置Intent跳转对象spec.setContent(intent);//添加标签页tabHost.addTab(spec);//创建Intent跳转对象intent = new Intent();intent.setClass(this, Mp3RemoteActivity.class);//新建一个标签页spec = tabHost.newTabSpec("mp3RemoteActivity");//设置标签名称,还可设置图片spec.setIndicator("远程");//设置Intent跳转对象spec.setContent(intent);//添加标签页tabHost.addTab(spec);}

3.4、构建设置界面

1.创建SettingActivity和对应布局文件activity_setting。

2.修改布局文件,在布局文件中主要添加Mp3文件目录的文本框。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="${relativePackage}.${activityClass}" >    <TextView        android:id="@+id/mp3PathTextview"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="@string/mp3Path" />    <EditText        android:id="@+id/mp3Path"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_below="@id/mp3PathTextview"        android:hint="@string/mp3Path" />    <Button        android:id="@+id/save"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:text="@string/save" /></RelativeLayout>

3.修改SettingActivity文件,在该类中主要是获取配置文件信息和保存设置信息到配置文件中。

package com.cygoat.mp3.activity;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.util.Properties;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.EditText;import android.widget.Toast;import com.cygoat.mp3.Mp3Contant;import com.cygoat.mp3.R;import com.cygoat.util.IOUtil;public class SettingActivity extends Activity {private EditText mp3PathText;private Button save;private Properties properties = new Properties();private static final String MP3_PATH = "mp3Path";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_setting);mp3PathText = (EditText) findViewById(R.id.mp3Path);save = (Button) findViewById(R.id.save);getSetting();showSetting();save.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {File file = new File(Mp3Contant.settingFileName);FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(file);properties.put(MP3_PATH, mp3PathText.getText().toString());properties.store(fileOutputStream, "mp3Path");Toast.makeText(SettingActivity.this, "保存成功", Toast.LENGTH_SHORT).show();Intent intent = new Intent();intent.setClass(SettingActivity.this, MainActivity.class);startActivity(intent);} catch (Exception e) {e.printStackTrace();} finally {IOUtil.close(fileOutputStream);}}});}private void showSetting(){mp3PathText.setText(properties.getProperty(MP3_PATH));}private void getSetting() {File file = new File(Mp3Contant.settingFileName);FileInputStream fileInputStream = null;try {if (!file.exists()) {//文件不存在则创建文件file.createNewFile();}//构建文件输入流fileInputStream = new FileInputStream(file);//将文件流装载进Properties对象中properties.load(fileInputStream);} catch (Exception e) {e.printStackTrace();} finally {IOUtil.close(fileInputStream);}}}

3.5、构建关于界面

创建AboutActivity和对应布局文件activity_about.xml。由于该类比较简单,详情可见源代码。

以上可能有Java部分的源代码没有去说明,像xml文件解析、远程文件下载、Properties文件解析和保存等。这些代码都属于Java部分,有兴趣的读者查看Java相关内容。

运行效果如下


Android实战——Mp3播放器_第2张图片



Android实战——Mp3播放器_第3张图片


Android实战——Mp3播放器_第4张图片


Android实战——Mp3播放器_第5张图片


源代码如附件

更多相关文章

  1. 参考:修改android开机界面
  2. android 程序启动界面的短暂黑屏解决办法
  3. android工程建立到最后一步提示unsupported template dependency
  4. 自己封装的Android sqlite-helper.jar包使用方法
  5. Android中扫描多媒体文件操作详解
  6. Android四种点击事件方法
  7. Android使用Parcelable传递对象方法及注意事项
  8. Android名词解释之什么是APK文件

随机推荐

  1. Android监听呼出电话
  2. Android(安卓)实用工具Hierarchy Viewer
  3. [记录点滴]在Ionic和Android中上传Blob图
  4. Android伸手党系列之四:Android项目开发常
  5. Android读写XML(下)——创建XML文档
  6. Android(安卓)中关于Cursor类的介绍
  7. android机制系列之七 Android(安卓)Camer
  8. Android(安卓)的invalidate 与postInvali
  9. Flutter 混合开发(Android)Flutter跟Native
  10. Android周学习Step By Step(4)--界面布局