相关链接

视频下载地址:http://www.verycd.com/topics/2837883/

源码下载地址:http://www.mars-droid.com/bbs/forum.php?mod=viewthread&tid=13&extra=page%3D1

附上本人练习用的Mp3Player源码:

05 Activity和Intent

 

新建一个layout(file),命名为other.xml(文件名小写),加入一个TextView,加ID属性,android:id=”@+id/myTextView”

 

新建一个java文件(类),命名为OtherActivity,继承extends Activity.

重写onCreate方法,右键,source-override-onCreate。加载对应的样式文件,setContentView(R.layout.other)。定义成员变量,private Textview myTextView=null;

找出TextView, myTextView = (TextView)findViewById(R.id.myTextvew);。
 
注册Activity。AndroidMainfest.xml中加入<activity android:name=".secondActivity" android:label="@string/app_name"></activity>
 
原Activity跳转:
mainButton.setOnClickListener(otherListener);
private OnClickListener otherListener = new OnClickListener() {
       @override
       public void onClick(View v){
              Intent intent = new Intent();
              intent.setClass(FirstActivity.this,SencondActivity.class);
              startActivity(intent);
}
}
//监听器另一种写法,定义内部类(内部类可以调用外部的成员变量)
class MyButtonListener implements OnClickListener{ @Override … (同上)}
绑定btn: btn.setOnClickListener(new MyButtonListener ());
 
实现跳转的基础上,加入数据传递:intent加入键值对
intent.putExtra(“testKey”,”testValue”);
在目标activity中取出:
Intent intent = getIntent();
String value = intent.getStringExtra(“testKey”);
另一种写法:
Bundle bun = intent.getExtras();
String value = bun.getString(“testKey”);
 
Intent跳转的不一定是一个Activity,甚至不是同一个应用程序里的东西
Uri uri = Uri.parse(“smsto://0800000123”); //Android自带的发短信程序
Intnet intent = new Intent(Intetn.ACTION_SENDTO,uri);
intnet.putExtra(“sms_body”,”sms_text”);
startActivity(intent);
 
06 常用控件
导入命名空间的快捷键是:ctrl + shift + o。import …
EditText,TextView,Button
xxx = (Button)findViewbyId(R.id.xxx);
xxx.setText(“controlname”);
 
整型和字符串转化:
int intOne = Integer.parseInt(strOne);
String strOne = intOne + “”;
 
点击menu按钮时的事件:重写事件onCreateOptionsMenu。
为menu加上菜单按钮:          
menu.add(0,1,1,R.string.exit);
menu.add(0,2,2,R.string.about);
为按钮添加事件:重写事件:onOptionsItemSelected
判断事件处理:
if(item.getItemId()==1){ finish(); } //itemid为1的按钮事件是finish(),退出。
 
07 Activity的生命周期
记录日志的方法:在函数里加入:System.out.println(“msg”);
测试时不起作用,再增加一行后正常: Log.e(“mytag”,”mymsg”);
 
启动第一个Activity,
FirstActivity:onCreate(创建),OnStart(开始,可见),OnResume(显示,获取焦点,可以点击,不被弹出窗遮挡)
 
从第一个activity启动第二个Activity(非窗体,完全遮挡):
FirstActivity:OnPause(暂停,保存数据),
SecondActivity:OnCreate,OnStart,OnResume,
FirstActivity:OnStop(完全被遮挡,如果第二个Activity是弹出框,即没有完全遮挡时,不会调用此方法)。
 
从第二个Activity返回:
SecondActivity:OnPause,
FirstActivity:OnReStart,OnStart,OnResume,
SecondActivity:OnStop,OnDestory(被销毁).
再进第二个:
FirstActivity:OnPause
SecondActivity:OnCreate,OnStart,OnResume
FirstActivity:OnStop
 
08 Activity的生命周期2
Task:Activity对象的栈结构(后进先出)
跳转Activity时,进栈,点击back,出栈
finish()函数会销毁Activity
在AndroidManifest.xml中声明Activity时,加入属性:
android:theme=”@android:style/Theme.Dialog”,此Activity会变成窗口模式。
导入源代码:右键,import,Genderal-Existing Projects into workspace,选择文件夹
 
09Activity布局初步1
智能提示快捷键:alt+/
LinearLayout.xml:
android:orientation:水平布局或垂(tga)直
xml注释写法:<!-- …. -->
android:gravity=”…” 制控此控件中的文本的位置(非此控件的位置)
android:textsize=”35pt”
android:background=”#aa0000”
android:paddingxxx=”20dip” //比px更好地适应不同的屏幕
android:layout_weight="2" //布局比例值,对于值为1的控件(同一父控件),此控件高度(或宽度)是它的两倍
android:singleLine="true" //文字超出长度时会以…代替
 
TableLayout.xml:
android:stretchColumns="0" //当控件不足以填充行时,第1列(序号从0起)拉伸
<TableRow><TextView…></TextView>…</TableRow> //简单布局,一个TextView相当于一个单元格
10Activity布局初步2
复杂布局的实现:LinearLayout和TableLayout嵌套使用。
< LinearLayout xx布局>
< LinearLayout 水平布局>…</ LinearLayout>
< LinearLayout 垂直布局>
       < TableLayout>…</ TableLayout>
</ LinearLayout>
</ LinearLayout>
 
11Activity布局初步3
RelativeLayout.xml
 
android:layout_ above ="@id/controlid" 将该控件的底部至于给定ID的控件( controlid )之上
类似的:layout_below,layout_toLeftOf,layout_toRightOf。把此控件放到xxx的上/下/左/右去
 
android:layout_alignBottom将该控件的底部边缘与给定ID控件的底部边缘
类似的:layout_alignLeft,layout_alignRight,layout_alignTop。把它们两个向上/下/左/右对齐
 
android:layout_alignParentLeft如果该值为true,则将该控件的左边与父控件的左边对齐
类似的:layout_alignParentRight,layout_alignParentTop
 
android:layout_centerHorizontal 如果值为真,该控件将被至于水平方向的中央
android:layout_centerInParent 如果值为真,该控件将被至于父控件水平方向和垂直方向的中央
android:layout_centerVertical 如果值为真,该控件将被至于垂直方向的中央
 
12常用控件2
RadioGroup,RadioButton,CheckBox,Toast(相当于一个alert的控件)
<RadioGroup…><RadioButton…/><RadioButton…/></RadioGroup>
单选定义点击事件,对Group添加:
genderGroup = (RadioGroup)findViewById(R.id.genderGroup);
femaleButton = (RadioButton)findViewById(R.id.femaleButton);
genderGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if(femaleButton.getId() == checkedId){ …}
}
});
 
为多选框添加事件,需要逐个添加
cbOne.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){…}
       }
});
 
Toast.makeText(ThisActivityName.this, "alertmsg", Toast.LENGTH_SHORT).show(); //显示信息
13常用控件3
ProgressBar进度条:
 
水平样式:style="?android:attr/progressBarStyleHorizontal",转圈样式:style="?android:attr/progressBarStyle"
初始化是否可见:android:visibility="gone",程序运行可见:BarObj.setVisibility(View.VISIBLE);
进度条android:max="200",总共有多少份进度。默认为100,程序设置:HorizontalBar.setMax(150);
HorizontalBar.setProgress(i); HorizontalBar.setSecondaryProgress(i + 10); //设置主进度和第二进度的当前值
 
ListView数据列表
public class Activity01 extends ListActivity{…} //继承ListActivity
 
ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>(); 
HashMap<String, String> map1 = new HashMap<String, String>(); 
map1.put("user_name", "zhangsan");
map1.put("user_ip", "192.168.0.1");
list.add(map1);
 
MyAdapter listAdapter = new MyAdapter(this, list, R.layout.user, new String[] { "user_name", "user_ip" }, new int[] { R.id.user_name,R.id.user_ip}); //当前activity,ArrayList,列表布局xml,list中HashMap的键名称,对应xml中的控件id(指定位置)
 
添加Item点击事件,override onListItemClick
 
14 Handler的使用1
Handler handler = new Handler(); //实现异步处理
handler.post(updateThread); //1点击事件中,调用Handler的post方法,将要执行的线程对象添加到队列中
Runnable updateThread = new Runnable() {
       public over run(){ //2将要执行的操作写在线程对象的run方法中
              System.out.println(“deal with data”); //数据处理
       handler.postDelayed(updateThread,3000); //3在run中循环执行,用postDelayed或post方法
}
}
 
可以定义更复杂的handler,如复写它的handleMessage方法。
Handler barHandler = new Handler(){
       public void handleMessage(Message msg){…} //处理barHandler用sendMessage方法压进来的队列msg
}
 
在要调用sendMessage的方法里面,定义上面的msg参数:
Message msg = updateHandler.obtainMessage(); //obtain,获得,取得
msg可以加入参数值,比如:
msg.arg1 = somevalue; //用arg1和arg2传值时系统耗能少些
Bundle bundle = new Bundle();
bundle.putString(“test”,”test bundle”);
msg.setData(bundle); //对应取出数据用Bundle bundle = msg.getData();
 
定义完msg之后,就可以把msg压到消息队列中去了:barHandler.sendMessage(msg);或者用msg.sendToTarget();
系统自动转向执行barHandler的handleMessage方法。
 
15 Handler的使用2
Handler.post方法添加线程对象(new Runnable(){…}),但他们是运行在同一个线程里的。因为post只调用了线程对象的run方法去执行,而没有调用start方法去开启另一个线程。
要异步执行线程,可以用Thread t = new Thread(线程对象); t.start();
也可以用HandlerThread类。先定义一个该类对象:
HandlerThread handlerThread = new HandlerThread(“handler_thread”);
handlerThread.start(); //在使用getLooper方法前调用
再定义一个要加入此对象的Handler对象:
class MyHandler extends Handler{…
       public MyHandler(Looper looper){
              super(looper);
}
public void handleMessage(Message msg){…}
…}
用Handler类来定义MyHandler类的对象:
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
定义这个handler的msg:Message msg = myHandler.obtainMessage(); … 然后msg.sendToTarget();
即把myHandler加入到了一个线程队列中,它将以一个新的线程运行。
 
16 SQLite
SQLite:很小的一个关系型数据库
SQLiteOpenHelper:帮助类
当需要操作SQLite数据库时,首先需要一个SQLiteOpenHelper类的对象。因为SQLiteOpenHelper是一个抽象类,我们必须先写一个类去继承它。用getReadableDatabase()或getWritableDatabase()去得到SQLiteDatabase对象。
 
继承类中必须有构造函数,如:
public DatabaeHeapler(Context context,String name,CursorFactory factory,int version){
       super(context,name,factory,version); //必须通过super调用父类中的构造函数
}
 
在继承类中,可以重写onCreate、onUpgrade两个回调函数。onCreate方法的调用,是在SQLiteOpenHelper对象调用getReadableDatabase()或getWritableDatabase()方法时执行(不是在创建SQLiteOpenHelper对象时就执行)。onCreate可以用来创建表,如:
public void onCreate(SQLiteDatabase db){
       db.execSQL(“create table user (id int,name varchar(20)”);
}
 
实例:
DatabaseHelper dbHelper = new DatabaseHelper(xxxActivity.this,”pro_dbname_db”,…); //生成的数据库名
SQLiteDatabase db = dbHelper.getReadableDatabase(); //得到android内置类的对象
当上面创建dbHelper对象的版本参数(整形,递增),发生变化时,会调用我们继承类中重写的onUpgrade 方法。
 
插入数据:
ContentValues values = new ContentValues(); //创建ContentValues对象
values.put("id", 1); //插入键值对,其中键是列名,值必须和数据库当中的数据类型一致
values.put("name","zhangsan");
DatabaseHelper dbHelper = new DatabaseHelper(xxxActivity.this," pro_dbname_db ",2); //2是版本号
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.insert("user", null, values);
 
cmd-adb shell命令,如果提示没有找到设备,原因是没有启动模拟器。
进行Linux命令行模式,用Linux命令来操作模拟器。
常用命令:ls –l 目录结构 cd 进入目录
cd data
cd data
列出了模拟器中的所有应用程序和它的包名。进入当前应用程序(包名mars.sqlite3)(在eclipse中run as)。
cd mars.sqlite3
ls
执行onCreateb函数,即创建数据库后ls,目录下多了个databases,进入databases,会看到新建的数据库
操作此数据库:
sqlite3 test_mars_db //sqlite3是一个命令,后接数据库名,命令提示符由#变成了sqlite
尝试一下命令 .schema(.开始),列表了表和创建表的语句(其中android_metadata是自带的)。
点击虚拟机的insert按钮插入数据后,select * from user; (记得用;结束)查看数据。
 
更新:
ContentValues values = new ContentValues();
values.put(“name”,”zhangsanfeng”); //意义:把name这一列的值改成zhangsanfeng
db.update(“user”,values,”id=?”,new String[]{“1”}); //表名,ContentValues对象,where,占位符参数值
 
查询:
Cursor cursor = db.query("user", new String[]{"id","name"}, "id=?", new String[]{"1"}, null, null, null);
//表名,查询的列,where占位语句,where参数值,group by ,having ,order by
while(cursor.moveToNext()){
       String name = cursor.getString(cursor.getColumnIndex(“name”)); …
}
//讲者建议不要过分信赖和依赖sqlite数据库,有时会出现莫名奇妙的问题。
 
17 调试程序
在java视图中(不是在ddms里)添加logcat:
window-show view-other-logcat,可以把它拉到想要的位置
添加过滤器:
Filter Name:标签名,如sysout,by Log Tag:日志标识,如System.out,把这个方法的输出写到日志里
可以加到某函数或语句的开始,判断是否执行到。
出现异常时,可以看log的error日志。
用Log类写日志:Log.d(“myDebug”,”myMsg”); //myDebug是by Log Tag的值
DDMS中有一个File Explorer,在启动虚拟机之后,会读取到里面的文件,可以做添加和取出操作。
 
18 下载文件
注意关闭输入输出流,加入权限配置。
下载文本:
private URL url=null; …
url = new URL(urlStr); //创建URL对象
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
buffer = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
//把得到的InputStream转成buffer类型
while ((line = buffer.readLine()) != null) {sb.append(line); }
下载文件:
创建目录:
File dir = new File(Environment.getExternalStorageDirectory() + “/”+ dirName);
dir.mkdirs();
创建文件:
File file = new File(Environment.getExternalStorageDirectory() + “/”+filename);
file.createNewFile(); //创建一个空文件
//将InputSteram的数据(input参数)写入SD卡
OutputStream output = new FileOutputStream(file); //取创建文件的outputstream
byte buffer[] = new byte[4*1024];
int temp;
while((temp = input.read(buffer))!=-1){ output.write(buffer, 0, temp ); } //往文件中写数据
return file; //返回File对象 
 
19 ContentProvider初步
1.提供为存储和获取数据提供了统一的接口
2.可以在不同的应用程序之间共享数据(SQLite只对同一应用程序)
3.Android为常见的一些数据提供了ContentProvider(包括音频,视频,图片和通讯录等等)
如果不要求在不同应用程序间共享数据,就没必要用ContentProvider,而使用数据库,文件,xml等存放数据。
 
20 XML文件解析
采用SAX(Simple API for XML)来解析。
DOM解析是把XML看成一棵树,把它全部加载进来解析,优点是操作比较方便,缺点是对大xml文件操作不合适。
对比SAX:逐行顺序读取解析,随时停止读取(已经读到所需信息)。缺点:操作复杂。对增删结点等操作不合适。
 
解析文档过程:
对于如下文档:<doc><para>Hello,world!</para></doc>
在解析文档的过程中会产生如下一系列事件:
start document
start element:doc
start element:para
characters:Hello,world!
end element:para
end element:doc
end document
1. 创建事件处理程序
2. 创建SAX解析器
3. 将事件处理程序分配给解析器
4. 对文档进行解析,将每个事件发送给处理程序
上面所说的事件存在于一个特列的SAX接口:ContentHandler,这个接口位于org.xml.sax包中。
 
实例:
SAXParserFactory factory = SAXParserFactory.newInstance(); //创建一个SAXParserFactory
XMLReader reader = factory.newSAXParser().getXMLReader(); //得到XMLReader
reader.setContentHandler(new MyContentHandler()); //为XMLReader设置内容处理器
reader.parse(new InputSource(new StringReader(xmlstring))); //开始解析xml
 
其中MyContentHandler是继承DefaultHanlder(用空方法实现了ContentHandler接口)的类:
public class MyContentHandler extends DefaultHandler{
       public void startDocument() throws SAXException { … } //抛出所有可能的异常,交由上层调用函数处理
       public void startElement(String namespaceURI,String localName,String qName,Attributes attr) throws SAXException {
              //当前读取结点的命名空间,没有前缀的标签名,带前缀的标签名,标签里的属性
              //如<abc:name id=”001”></abc:name>,这个abc就是前缀,name就是标签名,id就是属性
              nodeName = localName; //全局变量
if (localName.equals("worker")) { //worker结点,endElement方法一样,作判断条件用
for (int i = 0; i < attr.getLength(); i++) { //获取标签的全部属性
                            System.out.println(attr.getLocalName(i) + "=" + attr.getValue(i));
                     }
}
}
       public void characters(char[] ch,int start,int length) throws SAXException{ 
       if(nodeName.equals(“name”)){ //判断条件
       xxx = new String(ch,start,length); …
}
}
public void endElement(String namespaceURI,String localName,String qName) throws SAXException{…}
public void endDocument() throws SAXException { … }
}
 
21 广播机制1
自定类,继承自BroadcastReceiver,重写onReceive方法。
public class TestReceiver extends BroadcastReceiver{
       public TestReceiver(){ System.out.println("TestReceiver"); }
       @Override
       public void onReceive(Context context, Intent intent) {       System.out.println("onReceive"); } 
}
 
将receiver注册到android系统中:在AndroidManifest.xml加入reciver结点:
<receiver android:name=".TestReceiver">
       <intent-filter>
              <action android:name="android.intent.action.EDIT " /> //指定哪个接收器接收哪一个事件
       </intent-filter>
</receiver>
 
 
实例:
Intent intent = new Intent(); 
intent.setAction(Intent. ACTION_EDIT); //设置动作,对应AndroidManifest.xml中的Action
xxxActivtiy.this.sendBroadcast(intent); //发送广播
 
22 广播机制2
注册BroadcastReceiver的方法:
1.       在AndroidManifest.xml中注册
当应用程序被关闭之后,receiver仍会收到广播。比如监听电池的状态。
2.       在应用程序代码中注册。
使用receiver来更新Activity中的状态。
注册(Activity启动时):registerReceiver(receiver,filter) //相当于AndroidManifest.xml中的intent-filter
取消注册(Activity不可见时):unregisterReceiver(receiver)
实例:
smsReceiver = new SMSReceiver(); //生成自定义Receiver类,extends BroadcastReceiver
IntentFilter filter = new IntentFilter(); //生成IntentFilter对象
filter.addAction(SMS_ACTION); //添加Action
xxxActivity.this.registerReceiver(smsReceiver,filter); //注册
 
xxxActivity.this.unregisterReceiver(smsReceiver); //取消注册
 
在自定义的Receiver类中获取广播数据:
public void onReceive(Context context,Intent intent){
       Bundle bundle = intent.getExtras();  //接受intent对象中的数据
       Object[] myOBJpdus = (Object[])bundle.get(“pdus”); //Bundle对象中有一个属性名为pdus的obj数据
       SmsMessage[] messages = new SmsMessage[myOBJpdus.length]; //创建一个SmsMessage类型的数组
       for(int i=0;i<myOBJpdus.length;i++){
              message[i] = SmsMessage.createFromPdu((byte[])myOBJpdus[i]); //创建SmsMessage对象
              System.out.println(message[i].getDisplayMessageBody()); //消息内容
}
}
24 Socket编程
Socket,套接字,用于描术IP地址和端口,是一个通信链的句柄。
应用程序通过套接字向网络发出请求或者应答网络请求。
 
服务器端:
new ServerThread().start(); //对于服务器端的监听,使用新线程启动
class ServerThread extends Thread{
       public void run(){ //客户端采用TCP协议发送时
              ServerSocket serverSoket = null;
              serverSocket = new ServerSocket(4567); //创建ServerSocket对象,监听4567端口
              Socket socket = serverSocket.accept(); //接受请求
              InputStream inputStream = socket.getInputStream(); //用流的方式传输
              byte buffer[] = new byte[1024*4];
              int temp =0;
              while((temp = intputStream.read(buffer))!=-1){
       System.out.println(new String(buffer,0,temp)); //从inputStream中读取接收到的数据
}
}
public void run(){ //客户端使用UDP协议发送时
       DatagramSocket socket = new DatagramSocket(4567); //创建DatagramSocket对象
       byte data[] = new byte[1024];
       DatagramPacket packet = new DatagramPacket(data,data.length); //用包的形式传输
       socket.receive(packet);
       String result = new String(packet.getData(),packet.getOffset(),packet.getLength());
}
}
TCP客户端:
Socket socket = new Socket(“192.168.0.xx”,4567); //定义Socket对象
InputStream inputStream = new FileInputStream(“F://file/words.txt”); //读取文件流
OutputStream outputStream = socket.getOutputStream(); //socket的outputStream
byte buffer[] = new byte[4*1024];
int temp = 0;
while ((temp = inputStream.read(buffer))!=-1){
       outputStream.write(buffer,0,temp); //将inputStream的数据写到socket的outputStream中
}
UDP客户端:
DatagramSocket socket = new DatagramSocket(4567); // 创建DatagramSocket对象
InetAdress serverAddress = InetAddress.getByName(192.168.0.xx”);
byte data[] = “mystringword”.getBytes();
DatagramPacket packet = new DatagramPacket(data,data.length,serverAddress,4567);
socket.send(packet); //发送数据包
 
29实例代码-mp3播放器
新建项目。
values – strings.xml 中的app_name 不能是全小写
继承ListActivity,在layout的xml中加入<ListView android:id="@id/android:list"…/>
 
新建菜单:onCreateOptionsMenu
private static final int ITEMID_BTN_UPDATE = 1;
menu.add(0, ITEMID_BTN_UPDATE,1,R.string.updatelist); 
 
添加点击事件: onOptionsItemSelected
if(item.getItemId()==ITEMID_BTN_UPDATE){…}
下载xml文件:
ctrl + shift + F : 格式化代码
String xml = DownLoadXML("http://59.34.17.68:8099/ClientWeb/xml/resources.xml"); //调用下载文本类
添加用户权限:AndroidManifest.xml: <uses-permission android:name="android.permission.INTERNET" />
 
添加实体类:mp3info:
定义成员变量private String id; private String mp3Name; …
生成set和get方法:右键-Sources-Generate Getters and Setters,全选,确定。
生成构造函数:右键-Sources-Generate Constructor using Fields…,全选和全不选,分别确定,生成两个
生成toString()方法(方便调试):右键-Sources- Generate toString()…
 
添加xml操作类Mp3ListContentHandler extends DefaultHandler
重写characters endDocument endElement startDocument startElement五个方法(参考第20讲),摘取:
startElement:
tagName=localName; //记录当前的结点名
if(localName .equals("resource")){ mp3Info = new Mp3Info(); } //碰到数据结点开始时初始化对象
characters:
String temp = new String(ch, start, length);
if (tagName.equals("id")) { mp3Info.setId(temp); }
endElement:
if (qName.equals("resource")) { infos.add(mp3Info); } //添加结点信息
 
在Mp3LisrActivity中获取数据源:
private List<Mp3Info> parse(String xmlStr) {
       SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
       List<Mp3Info> infos = new ArrayList<Mp3Info>();
       XMLReader xmlReader = saxParserFactory.newSAXParser().getXMLReader();
       Mp3ListContentHandler mp3ListContentHandler = new Mp3ListContentHandler(infos); //传入infos
       xmlReader.setContentHandler(mp3ListContentHandler);
       xmlReader.parse(new InputSource(new StringReader(xmlStr))); //解释xml,把结果添加到infos中
       //for + ctrl + /,可以自动生成类foreach语句,用自动生成的实体toString方法可以把数据打印出来
       return infos;
}
 
新建显示数据的布局文件:
new-file:命名mp3info_item.xml,确定
加入两个TextView,设置好宽 android:layout_width,高android:layout_height,边距android:paddingLeft等。
<TextView android:id=”@+id/mp3_name”…/> 在R中会生成一个mp3_name的id
 
绑定数据,见13讲。
List<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>(); //定义空数据源
// for + ctrl + /,选择 for- iterate over collection,将list换成数据源Mp3Infos(要历遍的lists)
//将HashMap<String, String>找成lists中的实体类,Mp3Info mp3Info = (Mp3Info) iterator.next();
HashMap<String, String> map = new HashMap<String, String>(); //在for中填充空数据源
map.put("name", mp3Info.getName()); map.put("size", mp3Info.getSize()); list.add(map);
 
下面要实现下载文件,见18讲:
文件下载类:
SDCardRoot = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator; //根目录
File dirFile = new File(SDCardRoot + dir + File.separator);   dirFile.mkdirs(); //创建目录
File file = new File(SDCardRoot + dir + File.separator + fileName);  file.createNewFile(); //创建空文件
将InputStream里面的数据写到文件中:
File file = CreateFileInSDCard(fileName, path); //创建文件
OutputStream output = new FileOutputStream(file); //取得文件的outputstream
//将inputstream写入file中
byte buffer[] = new byte[4*1024];
int temp;
while((temp = input.read(buffer))!=-1) { output.write(buffer,0,temp); }
 
点击事件,ListActivity中重写onListItemClick:
//在oncreate方法中获取到列表数据源infos,在此方法中就可以用position得到点击的对象了。
Mp3Info mp3Info = infos.get(position); //得到点击对象
 
下载Mp3文件:
sysout + ctrl + / = System.out.println(“”);
1.       需要创建一个Service,把mp3对象传到service当中去。因为Service不依赖于Activity界面,优先级比较高,保证下载程序不会被关掉。
2.       每个文件的下载都需要在一个独立的线程当中进行。防止主线程阻塞。
 
创建Service:
 
包名前缀取ListActivity的包名,如dachun.mp3player.service,才能方便在AndroidManifest.xml中注册。
public class DownloadService extends Service,重写方法onBind,onStartCommand。
在AndroidManifest.xml的application结点中注册service:
<service android:name=".service.DownloadService"></service>
 
回到ListActivity的onListItemClick事件中,需要把mp3Info对象通过Intent传到Service中去:
先对Mp3Info类进行序列化:
public class Mp3Info implements Serializable …
点击行左边的黄色提示图标,选择Add default serial version ID,添加一个序列ID
然后补充ListActivity点击事件:
Intent intent = new Intent();
intent.putExtra(“mp3Info”,mp3Info);   //传递mp3Info对象
intent.setClass(this, DownloadService.class); //绑定Service
startService(intent); //启动Serivce
 
在Service的onStartCommand方法(每次startService都会调用):由intent参数可得到传过来的mp3info对象:
Mp3Info mp3Info = (Mp3Info)intent.getSerializableExtra("mp3Info"); //可以打印调试下
 
用线程类(如下)去下载。故在Service中创建线程内部类:
class DownloadThread implements Runnable,创建带Mp3Info对象的构造函数进行初始化,覆写run方法:
String mp3Url = "http://59.34.17.68:8099/ClientWeb/source/"+ mp3Info.getName();
HttpDownloader httpDownloader = new HttpDownloader();
int result = httpDownloader.downFile(mp3Url, "mp3/", mp3Info.getName()); //下载文件方法
 
继写onStartcommand方法:
DownloadThread downloadThread = new DownloadThread(mp3Info);
Thread thread = new Thread(downloadThread); //新建线程
thread.start(); //启动下载线程
记得创建目录时要配置权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
下载成功之后,可以用adb shell,ls –l进入sdcard-mp3中查看是否真的存在了下载的mp3文件。
 
31实例代码-续
使用tab分别显示本地下载文件和服务器可下载文件。
新建MainActivity extends TabActivity,用来实现tab,并在AndroidManifest.xml中注册。
<activity android:label="@string/app_name" android:name=".MainActivity" >
新建LocalMp3ListActivity extends ListActivity,用来显示本地文件。同理进行注册。
配置启动显示的Activity:有<intent-filter>结点的就是启动时显示的,把它的android:name设为.MainActivity。把之前注册的.MainActivity改成修改前的android:name(.Mp3ListActivity)。 
 
alt + shift + R,重命名文件。
将之前的main.xml(用作服务器文件列表)重命名为remote_mp3_list.xml。
新建main.xml作为MainActivity的layout文件:
<TabHost …><LinearLayout…>
<TabWidget…></TabWidget><FrameLayout…></FrameLayout>
</LinearLayout></TabHost>
 
为MainActivity设置layout,重写onCreate方法:setContentView(R.layout.main);
创建Tab对象显示:
TabHost tabHost = getTabHost(); //得到tab主控件对象
TabHost.TabSpec remoteSpce = tabHost.newTabSpec("Remote"); //添加选项卡
Resources res = getResources(); //设置图标
remoteSpce.setIndicator("Remote", res.getDrawable(android.R.drawable.stat_sys_download));
Intent remoteIntent = new Intent(); //设置显示的Activity
remoteIntent.setClass(this, Mp3ListActivity.class);
remoteSpce.setContent(remoteIntent);
tabHost.addTab(remoteSpce); //添加Tab
同理,创建另一个tab,显示本地文件。localSpce,localIntent,android.R.drawable.stat_sys_upload。
由于在本地有在服务器上的显示样式一样,复制一份remote_mp3_list.xml,重命名local_mp3_list.xml即可。
 
在FileUtils中添加函数List<Mp3Info> getMp3Files,用来读取目录中的mp3名字和大小,传入参数path:
File file = new File(SDCardRoot + File.separator + path); //得到当前路径的file对象,用来操作目录文件
File[] files = file.listFiles(); //列出所有文件,
if (files[i].getName().endsWith("mp3")) { … } //files[i].getName()和files[i].length()就是名字和大小了
 
LocalMp3Activity中:
setContentView(R.layout.local_mp3_list); //设置布局
覆写onResume,读取文件并绑定显示:
FileUtils fileUtils = new FileUtils(); //文件操作类
List<Mp3Info> mp3Infos = fileUtils.GetMp3Files("mp3/");
List<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>(); //作绑定数据源
for (Iterator iterator = mp3Infos.iterator(); iterator.hasNext();) { //历遍添加list子项
Mp3Info mp3Info = (Mp3Info) iterator.next();
HashMap<String,String> map = new HashMap<String,String>();
map.put("mp3_name", mp3Info.getName());
map.put("mp3_size", mp3Info.getSize());
list.add(map);
}
//cmd – adb shell – cd sdcard – cd mp3 – rm a1mp3 删除a1.mp3
 
32实例代码-续
实现点击local列表中的mp3时打开另一个activity并播放。新建activity:PlayerActivity extends Activity,注册。
 
覆写LocalMp3ListActivity的onListItemClick函数:
if (mp3Infos != null) { //mp3Infos在onResume时赋值,显示在列表中
Mp3Info mp3Info = mp3Infos.get(position); //获得点击的mp3
Intent intent = new Intent();
intent.putExtra("mp3Info", mp3Info); //传数据
intent.setClass(this, PlayerActivity.class);
startActivity(intent);
}
 
PlayerActivity:
添加播放、暂停、停止,图标,新建布局文件player.xml:
<LinearLayout…>
       <ImageButton android:id="@+id/start" android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:src="@drawable/start"/>
<ImageButton…/><ImageButton…/>
</LinearLayout>
 
覆写onCreate:
setContentView(R.layout.player); //显示图标
Intent intent = getIntent(); //取得参数
Mp3Info mp3Info = (Mp3Info)intent.getSerializableExtra("mp3Info"); //获取数据
startButton = (ImageButton)findViewById(R.id.start); //取得按钮 
startButton.setOnClickListener(new StartButtonListener()); //为按钮添加事件
 
内部监听类定义如下:
class StartButtonListener implements OnClickListener{
       public void onClick(View v){
       if(!isPlaying){ //boolean值,标识是否在播放
       String path = GetMp3Path(mp3Info);
       mediaPlayer = MediaPlayer.create(PlayerActivity.this,Uri.parse(“file//”+path)); //新建操作类
       mediaPlayer.start();
}
}
}
private String GetMp3Path(Mp3Info mp3Info){ //sd卡中存放mp3的路径
       String SDCardRoot = Enviroment.getExternalStorageDirectory().getAbsolutePath(); //sd卡路径
       return SDCardRoot + File.separator + “mp3” + File.separator + mp3Info.getName();
}
 
33实例代码-续
将mp3播放功能转移到Service中。(Activity并不稳定)
对LRC歌词文件进行预处理。
读取LRC文件(时间+歌词)。
使用Handler动态的更新歌词(歌词同步)
 
新建一个PlayerService extends Service,覆写onStartCommand方法(记得要注册)。
Mp3Info mp3Info = (Mp3Info) intent.getSerializableExtra("mp3Info"); //获取对象
int MSG = intent.getIntExtra("MSG", 0); //获取动作
//play,pause,stop是从上节的按钮监听器中移过来的方法
if (MSG == AppConstant.PlayMsg.PLAY_MSG) {play(mp3Info);}   //PLAY_MSG是整形常量
else if (MSG == AppConstant.PlayMsg.PAUSE_MSG) {pause();} 
else if (MSG == AppConstant.PlayMsg.STOP_MSG) {stop(); }
 
原PlayerActivity中的监听事件改成:
Intent intent = new Intent();
intent.setClass(PlayerActivity.this, PlayerService.class);
intent.putExtra("mp3Info",mp3Info);
intent.putExtra("MSG", AppConstant.PlayMsg.PLAY_MSG);
startService(intent);
 
函数添加注释的方法:/ + * + * + 回车,类似///
 
同步歌词处理:
创建一个LrcProcessor处理类,放到lrc包内。
public ArrayList<Queue> process(InputStream inputStream){…},此方法接收歌词文件的input做参数,返回一个队列数组,一个Queue<Long>存放时间,一个Queue<String>存放歌词,部分代码:
InputStreamReader inputStreamReader = new InputStreamReader(inputStream); //读取utf-8格式的lrc
BufferedReader bufferedReader = new BufferedReader(inputReader); //bufferedReader对象每次读一行
Pattern p = Pattern.compile(“正则”); //Matcher m = p.matcher(“字符串”);,if(m.find()){ …m.group(); …}
temp = bufferedReader.readLine(); //读一行,if(temp!=null)..
 
PlayerActivity的layout中添加一个TextView:lrcText用来显示歌词。
启动虚拟机,打开DDMS,进入mnt – sdcard – mp3,把歌词文件复制进去。
 
用class UpdateTimeCallback implements Runnable类去实现歌词切换:
public void run(){
       long offset = System.currentTimeMills() – begin; //计算时间偏移量
       …nextTimeMill = (Long)times.poll(); //从Queue对象中取出一个值
       … handler.postDelayed(updateTimeCallback,10); //本类对象
}
 
在StartButtonListener中调用UpdateTimeCallback去处理:
LrcProcessor lrcProcessor = new LrcProcessor();
queues = lrcProcessor.process(inputStream); //从文件中解释出time和message
updateTimeCallback = new UpdateTimeCallback(queues); //循环调用,控制textview显示
 
//出现waiting for debugger,不关拟虚机,关了eclipse再开,再运行调试
 
34实例代码-续
实现歌词在界面切出切入时的控制。使用BroadcastReceiver来实现,PlayActivity只在接收到广播时做动作。
ctrl + 左键,点击类名可转到定义
在PlayActivity中:
1.定义广播接收器calss LrcMessageBroadcastReceiver extends BroadcastReceiver。
覆写onReceive方法,取歌词信息:intent.getStringExtra(“lrcMsg”),设置到lrcTextView中显示。
2.定义intentFileter。可以在AndroidManifest.xml中注册,也可以在代码中生成:
intentFilter = new IntentFilter();
intentFilter.addAction(AppConstant.LRC_MESSAGE_ACTION); //由自定义常量定义动作
3.覆写onPause:unregisterReceiver(receiver); 
和onResume方法:
receiver = new LrcMessageBroadcastReceiver(); //用自定义广播类实例化对象
registerReceiver(receiver, getIntentFileter()); //getIntentFileter自定义方法,注册intentFileter(见上)