Android开发视频教学_读书笔记

相关链接

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

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

附上本人练习用的Mp3Player源码:https://files.cnblogs.com/vipcjob/Mp3Player.rar

05 ActivityIntent

 

新建一个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(见上)

 

 

 

posted @ 2011-12-05 20:47  DaCHun  阅读(1207)  评论(0编辑  收藏  举报