一.一个问题
有这样一个问题值得我们思考,若把一些类似于下载的功能(既耗时且不一定有结果)写在Activity(主线程)里,会导致Activity阻塞,长时间无响应,直至页面假死(如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭")。因此,我们需要把这些耗时的操作放在单独的子线程中操作。这就是Handler的使命。Handler提供异步处理的功能,发送和接收不是同时的(Activity的主线程和线程队列里的线程是不同的线程,并行进行,互不影响)。
二.Handler简介
Handler 为Android操作系统中的线程通信工具,它主要由两个作用:(1)安排消息或Runnable 在某个主线程中某个地方执行(2)安排一个动作在另外的线程中执行。每个Handler对象维护两个队列(FIFO),消息队列和Runnable队列,都是有Android操作系统提供的。Handler可以通过这两个队列来分别:
Handler的使用方法大体分为3个步骤:1.创建Handler对象。2.创建Runnable和消息。3.调用post以及sendMessage方法将Runnable和消息添加到队列。
三.Runnable队列
1.java中的线程
在java中,线程的创建有两种方法:继承Thread类和实现Runnable接口。而这最重要的都是要复写run方法来实现线程的功能。当线程的时间片到了,开始运行时,就执行run()函数,执行完毕,就进入死亡状态。
举个创建线程的例子:
Runnable r=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("thread");
handler.postDelayed(thread, 3000);
}
};
2.关于Runnable队列
(1)原理
Android的线程异步处理机制:Handler对象维护一个线程队列,有新的Runnable送来(post())的时候,把它放在队尾,而处理Runnable的时候,从队头取出Runnable执行。当向队列发送一个Runnable后,立即就返回,并不理会Runnable是否被执行,执行是否成功等。而具体的执行则是当排队排到该Runnable后系统拿来执行的。这就好比邮局的例子。寄信者将信写好后放入邮筒就回家了,他并不知道邮件何时被邮局分发,何时寄到,对方怎样读取这些事。这样,就实现了Android的异步处理机制。
(2)具体操作
向队列添加线程:
handler.post(Runnable );将Runnable直接添加入队列
handler.postDelayed(Runnable, long)延迟一定时间后,将Runnable添加入队列
handler.postAtTime(Runnable,long)定时将Runnable添加入队列
终止线程:
handler.removeCallbacks(thread);将Runnable从Runnable队列中取出
四.消息队列
1.消息对象
(1)Message对象
Message对象携带数据,通常它用arg1,arg2来传递消息,当然它还可以有obj参数,可以携带Bundle数据。它的特点是系统性能消耗非常少。
初始化: Message msg=handler.obtainMessage();
(2)Bundle对象
Bundle是Android提供的类,可以把它看做是特殊的Map,即键值对的包。而它特殊在键和值都必须要是基本数据类型或是基本数据类型的数组(Map的键值要求都是对象),特别的,键要求都是String类型。用Message来携带Bundle数据:
放入:msg.setData(Bundle bundle);
取出:msg.getData();
2.关于消息队列
(1)原理
Android的消息异步处理机制:Handler对象维护一个消息队列,有新的消息送来(sendMessage())的时候,把它放在队尾,之后排队到处理该消息的时候,由主线程的Handler对象处理(handleMessage())。整个过程也是异步的,和Runnable队列的原理相同。
(2)具体操作:
向队列添加Runnable:
handler.sendMessage(Message);将消息发送到消息队列
msg.sendToTarget();同上
handler.sendMessageDelayed(Message,long);延迟一定时间后,将消息发送到消息队列
handler.sendMessageAtTime(Message,long)定时将消息发送到消息队列
msg.sendToTarget();
处理消息:
消息的具体处理过程,需要在new Handler对象时使用匿名内部类重写Handler的handleMessage(Message msg)方法,如下:
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
。。。。。。
。。。。。。
}
};
五.Handler的两个作用
1.安排消息或Runnable 在某个主线程中某个地方执行
代码示例:
public class HandlerTestActivity extends Activity {
private Button start;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.handlertest);
start=(Button) findViewById(R.id.start);
start.setOnClickListener(new startListener());
System.out.println("Activity Thread:"+Thread.currentThread().getId());
}
Handler handler=new Handler();
Runnable thread=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("HandlerThread:"+Thread.currentThread().getId());
}
};
class startListener implements OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
handler.post(thread);
}
}
}
这个小程序中,首先程序启动,进入onCreate(),打印出当前线程(即主线程)的ID,之后点击按钮start,会将线程thread添加到线程队列,执行线程thread,thread的作用就是打印出当前线程的ID。在这个程序中,我们可以看到通过Handler我们可以实现安排Runnable 在某个主线程中某个地方执行,即作用(1)。
不过这里有个小小的陷阱,你发现了吗?这个程序看上去似乎实现了Handler的异步机制, handler.post(thread)似乎实现了新启线程的作用,不过通过执行我们发现,两个线程的ID相同!也就是说,实际上thread还是原来的主线程,由此可见,handler.post()方法并未真正新建线程,只是在原线程上执行而已,我们并未实现异步机制。
2.安排一个动作在另外的线程中执行。
(1)java中标准的创建线程的方法
第一步:
Runnable r=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("thread");
handler.postDelayed(thread, 3000);
}
};
第二步:
Thread t=new Thread (r);
第三步:
t.start();
若把上面示例程序中的handler.post(thread);语句改成以上形式,通过打印我们可以看到,两个ID是不同的,新的线程启动了!
(2)关于Looper
Looper类用来为线程开启一个消息循环,作用是可以循环的从消息队列读取消息,所以Looper实际上就是消息队列+消息循环的封装。每个线程只能对应一个Looper,除主线程外,Android中的线程默认是没有开启Looper的。
通过Handler与Looper交互,Handler可以看做是Looper的接口,用来向指定的Looper发送消息以及定义处理方法。默认情况下Handler会与其所在线程的Looper绑定,即:
Handler handler=new Handler();等价于Handler handler=new Handler(Looper.myLooper());
Looper有两个主要方法:
Looper.prepare();启用Looper
Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。
注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。
(3)Handler异步机制的实现
Handler是通过HandlerThread 使得子线程与主线程分属不同线程的。实际上,HandlerThread 是一个特殊的线程,它是一个封装好Looper的线程,
代码示例:
//创建一个名叫handler_hread的HandlerThread 对象 HandlerThread handlerThread=new HandlerThread("handler_hread"); //开启handlerThread,在使用handlerThread.getLooper()之前必须先调用start方法,否则取出的是空 handlerThread.start(); //将handler绑定在handlerThread的Looper上,即这个handler是运行在handlerThread线程中的 myHandler handler=new myHandler(handlerThread.getLooper()); Message message=handler.obtainMessage(); message.sentToTarget(); class myHandler extends Handler{ public myHandler(){} public myHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub System.out.println("Activity Thread:"+Thread.currentThread().getId()); } }
这样,就实现了handler的异步处理机制,在调用handler.post()方法,通过打印线程ID可以得知,子线程与主线程是分属不同线程的
/*********************************************************************程序开始****************************************************************************/
#include <linux/module.h> /* Every Linux kernel module must include this head */
#include <linux/init.h> /* Every Linux kernel module must include this head */
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* struct fops */
#include <linux/errno.h> /* error codes */
#include <linux/cdev.h> /* cdev_alloc() */
#include <asm/io.h> /* ioremap() */
#include <linux/ioport.h> /* request_mem_region() */
#include <linux/slab.h>
#include <asm/ioctl.h> /* Linux kernel space head file for macro _IO() to generate ioctl command */
#ifndef __KERNEL__
#include <sys/ioctl.h> /* User space head file for macro _IO() to generate ioctl command */
#endif
#define DEV_AUTHOR "hurryliu<hurryliu@126.com>"
#define DEV_DESC "fl2440 led driver"
#define DEV_NAME "led"
#define DEV_NUM 4
#define NUM 4
#define ERR -1
#define S3C_GPB_BASE 0x56000010 // 定义led的控制寄存器的基地址和偏移
#define S3C_GPB_LEN 0x10
#define GPBCON_OFFSET 0
#define GPBDAT_OFFSET 4
#define GPBUP_OFFSET 8
#define PLATDRV_MAGIC 0x1C
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
/*定义一个魔数。魔数有着特殊的功能。我们定义了led_on和led_off,但是这个宏定义可能和系统的别的重复,因此我们采用魔数机制,定义一个系统未用的魔数,然后让魔数生成我们定义的led_on和led_off,这样,我们的定义就不会和系统的相同了。*/
#ifndef LED_MAJOR
#define LED_MAJOR 0
#endif
//定义默认的主设备号为0,一般这个定义的设备号是固定不可用的,但是这为自动分配主设备号的逻辑提供了方便。
int led_num = 4; //定义设备个数
static int LED[NUM] = {5,6,8,10}; //定义设备在datasheet的位置
int led_major = LED_MAJOR;
int led_minor = 0;
static volatile unsigned long gpb_con, gpb_dat;
#define s3c_gpio_write(value, reg) __raw_writel((value), (reg)+fl2440_gpb_membase)
#define s3c_gpio_read(reg) __raw_readl((reg)+fl2440_gpb_membase)
/*定义宏,这里用到了__raw_writel和__raw_readl两个宏,这里对这两个宏的功能不做详细介绍,简单的讲就是读操作和写操作寄存器*/
static volatile unsigned long gpb_con, gpb_dat;
static void __iomem *fl2440_gpb_membase;
static struct cdev *led_cdev;
static int fl2440_hw_led_init(void)
{
if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN,DEV_NAME))
/* 申请内存。注意:这里的内存是FL2440中实际的物理内存,他对应了与LED的相关的寄存器*/
{
return ERR;
}
if( !(fl2440_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )
/* 建立物理内存到虚拟内存的映射。注意:在内核启动之后,所有对内存的操作都是虚拟的,如果要操作实际的物理内存,那么就要使用ioremap建立映射关系*/
{
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);/*申请内存有可能失败,如别的程序占用此资源等原因。这时候就必须要停止申请*/
return ERR;
}
gpb_con = s3c_gpio_read(GPBCON_OFFSET);
gpb_con &= ~((3<<20)|(3<<16)|(3<<12)|(3<<10)); /* 清零相关位*/
gpb_con |= ((1<<20)|(1<<16)|(1<<12)|(1<<10)); /* 设置为OUTPUT模式*/
s3c_gpio_write(gpb_con, GPBCON_OFFSET);
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
gpb_dat |= ((1<<10)|(1<<8)|(1<<6)|(1<<5)); /* default: This port set to low level, then turn LED on */
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
return 0;
}
static void fl2440_hw_exit(void)
{
{
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
gpb_dat |= ((1<<10)|(1<<8)|(1<<6)|(1<<5)); /*turn off the all led */
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);/* 程序退出的时候,要释放申请的内存 */
iounmap(fl2440_gpb_membase); /* 解除映射关系*/
}
static int led_open(struct inode *inode, struct file *file) //如果在应用空间调用open函数,就会调用led_open,打开相应的设备节点
{
int minor = iminor(inode); /* iminor函数能够将设备节点的次设备号获取出来,根据次设备号,就可以操作该设备*/
file->private_data =(void *)minor;
printk("/dev/led%d opened.\n",minor);
return 0;
}
static int led_release(struct inode *inode,struct file *file) //如果在应用程序空间调用close函数,就会调用led_release,关闭节点
{
printk("/dev/led%d closed.\n",iminor(inode));
return 0;
}
static void led_on(int which_led)
{
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
gpb_dat &= ~(0x01<<LED[which_led]); /*turn on the all led */
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
static void led_off(int which_led)
{
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
gpb_dat |= (0x01<<LED[which_led]); /*turn off the all led */
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
static long led_ioctl(struct file *file,unsigned int cmd, unsigned long args)
{
int which = (int)file->private_data;
switch(cmd)
{
case LED_ON:
led_on(which);
break;
case LED_OFF:
led_off(which);
break;
default:
printk("%s is not support ioctl command=%d!\n", DEV_NAME, cmd);
break;
}
return 0;
}
static struct file_operations led_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static int fl2440_sw_led_init(void)
{
int result;
dev_t dev_m;
if(led_major != 0)
{//典型的手动分配主设备号
dev_m = MKDEV(led_major,0);
/* 将主设备号和次设备号转换成dev_t类型 */
result = register_chrdev_region(dev_m,led_num,DEV_NAME);/*to appoint minor number to a device*/
}//为一个字符驱动获取一个或多个设备编号来使用,dev_m是起始的设备编号,通常为零,led_num是有多少个设备
else
{
result = alloc_chrdev_region(&dev_m,led_minor,led_num,"led");/* dynamic alloc the device number */
led_major = MAJOR(dev_m);
}//动态分配设备编号,第一个参数是设备,第二个是参数是第一次设备号,一般为零,第三个参数是设备个数,
if(result < 0)
{
printk("led major allocation failure!\n");
printk("fl2440 %s driver use major number%d.\n", DEV_NAME, led_major);
return ERR; //动态分配失败
}
printk("fl2440 %s driver use major number %d.\n", DEV_NAME, led_major);
if(NULL == (led_cdev=cdev_alloc())
) /* Register independent character device */
{
printk(KERN_ERR "fl2440 can't register device for led!\n");
unregister_chrdev_region(dev_m,led_num);
/*release the dev*/
return ERR;//分配失败的时候,要取消分配
}
led_cdev->owner = THIS_MODULE;
cdev_init(led_cdev, &led_fops); /* 初始化设备*/
if(0!=(result = cdev_add(led_cdev, dev_m, led_num))) /* add a character device to the system */
{
printk(KERN_INFO "led driver can't reigster cdev! ");
goto error;
}
return 0;
error: //如果在注册的过程中出现失败,那么程序转到此处,反向注销
cdev_del(led_cdev); /* delete a character device */
unregister_chrdev_region(dev_m,led_num);
return result;
}
static int __init fl2440_led_init(void)
/*驱动进来的第一个函数,此函数有module_init指定。在这里,我喜欢放两个函数,一个函数是硬件的初始化,包括内存申请,物理地址和虚拟地址的映射,寄存器的初始化等工作;另一个函数是软件的初始化,包括主次设备号的申请,设备的注册和添加,设备的初始化等工作。*/
{
int result;
printk("led module install in your system!\n");
result=fl2440_hw_led_init();
if(result != 0)
{
printk("led hardware init failure!\n");
return ERR;
}
printk("led hardware init succeed!\n");
result=fl2440_sw_led_init();
if(result != 0)
{
printk("led software init failure!\n");
return ERR;
}
printk("led software init succeed!\n");
return 0;
}
static void __exit fl2440_led_exit(void)
/*驱动退出时候执行的函数,由module_exit指定,完成一些扫尾工作,比如释放cdev占用的内存,释放原先申请的设备号等。它像你忠实的仆人,为你做清洁,不过记得给小费哦。*/
{
dev_t dev_m = MKDEV(led_major, led_minor);
fl2440_hw_exit();
cdev_del(led_cdev);
unregister_chrdev_region(dev_m,led_num);
printk("led module removed from your system!\n");
return ;
}
module_init(fl2440_led_init);
module_exit(fl2440_led_exit);
MODULE_AUTHOR(DEV_AUTHOR);
MODULE_DESCRIPTION(DEV_DESC);
MODULE_LICENSE("GPL");
/*********************************************************************程序结束***************************************************************************************/
与主次设备号相关的3个宏:
MAJOR(dev_t dev):根据设备号dev获得主设备号;
MINOR(dev_t dev):根据设备号dev获得次设备号;
MKDEV(int major, int minor):根据主设备号major和次设备号minor构建设备号。
1.使用AudioRecord录制原始音频
除了通过意图启动录音机和使用MediaRecorder之外,Android还提供了第三种方法来捕获音频:使用成为AudioRecord的类。AudioRecord是三种方法里最灵活的(因为允许访问原始音频流),但是它拥有的内置功能也是最少的,如不会自动压缩音频。
使用AudioRecord的基础知识非常简单。我们只需要构造一个AudioRecord类型的对象,并传入各种不同的配置参数。
需要指定的第一个值是音频源。下面使用的值与用于MediaRecorder的值相同,其在MediaRecorder.AudioSource中定义。实际上,这意味着可以使用MediaRecorder.AudioSource.MIC。
int audioSource=MediaRecorder.AudioSource.MIC;需要指定的第二个值是录制的采样率。以赫兹(Hz)为单位指定它。MediaRecorder的采样音频是8kHz,而CD质量的音频通常是44.1Hz。不同的安卓手机硬件能够以不同的采样率进行采样。而本示例则以11025Hz的采样率进行采样,这是另外一个常用的采样率。
int frequency = 11025;
接下来,需要指定的第三个值是捕获的音频通道的数量。在AudioFormat类中指定了用于此参数的常量,而且可根据名称理解它们。
AudioFormat.CHANNEL_CONFIGURATION_MONO;//channelConfiguration AudioFormat.CHANNEL_CONFIGURATION_STEREO; AudioFormat.CHANNEL_CONFIGURATION_INVALID; AudioFormat.CHANNEL_CONFIGURATION_DEFAULT;随后,需要指定的第四个值是音频的格式。在 AudioFormat类中也指定了以下各种可能的常量。
AudioFormat.ENCODING_DEFAULT;//audioEncoding AudioFormat.ENCODING_INVALID; AudioFormat.ENCODING_PCM_16BIT; AudioFormat.ENCODING_PCM_8BIT;PCM代表脉冲编码调制,它实际上是原始的音频样本。16位将占用更多的空间和处理能力,但是表示的音频将更接近真实。
最后,需要指定的是第五个值是缓冲区大小。实际上可以查询AudioRecord类以获得最小缓冲区大小,查询方式是调用getMinBufferSize静态方法,同时传入采样率、通道配置以及音频格式。
int bufferSize = AudioTrack.getMinBufferSize(frequency,channelConfiguration, audioEncoding);完成了以上步骤,我们就可以构造实际的AudioRecord对象。
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);但值得注意的是,AudioRecord类实际上并不保存捕获的音频,因此需要手动保存捕获的音频。可以首先利用以下方法来创建一个文件。
File path = new File( Environment.getExternalStorageDirectory().getAbsolutePath() + "/AudioRecorder/files/"); path.mkdirs(); try { recordingFile = File.createTempFile("recording", ".pcm", path); } catch (IOException e) { throw new RuntimeException("Couldn't create file on SD card", e); }接下来再创建该文件对应的OutputStream输出流,尤其是出于性能和便利的原因,可以将它包装在BufferedOutputStream和DataOutputStream中。
DataOutputStream dos = new DataOutputStream( new BufferedOutputStream(new FileOutputStream( recordingFile)));现在就可以启动捕获,同时将音频样本写入到文件中。可以使用short数组来保存从AudioRecord对象读取而来的音频。同时,将采用比AudioRecord对象的缓冲区更小的数组,从而在确保将音频读出来之前缓冲区没有被填满。
为了确保此数组小于缓冲区大小,需要将缓冲区大小除以4.因为缓冲区的大小以字节为单位,而每个short类型的数据占用2个字节,所以除以2并不足够。所以除以4就刚好使得该数组是AudioRecord对象内部缓冲区大小的一半了。
short[] audiodata = new short[bufferSize / 4];至此,只需要调用AudioRecord对象上的startRecording方法即可。
录制开始之后,可以构造一个循环,不断从AudioRecorder对象读取音频并放入short数组中,同时写入对应文件的DataOutputStream
while (isRecording) { int bufferReadResult = audioRecord.read(buffer, 0,bufferSize); for (int i = 0; i < bufferReadResult; i++) { dos.writeShort(buffer[i]); } }当想结束录音的时候,调用AudioRecord对象上的stop方法和DataOutputStream上的close方法。
2.使用AudioTrack来播放原始音频
AudioTrack允许播放AudioRecord捕获的原始音频类,而它们并不能使用MediaPlayer对象播放。
为了构造一个AudioTrack对象,需要传入以下一系列配置变量来描述待播放的音频
第一个参数是流类型。可能的值定义为AudioManager类的常量。例如
AudioManager.STREAM_MUSIC//正常播放音乐的音频流第二个参数是播放音频数据的采样率,这里的参数需要和AudioRecord指定的参数一致。
第三个参数是通道配置。可能的值与构造AudioRecord的值相同。
第四个参数是音频格式。可能的值与构造AudioRecord的值相同。
第五个参数是将在对象中用于存储音频的缓冲区大小。为了确定使用最小的缓冲区大小,可以调用getMinBufferSize方法,同时传入采样率、通道配置和音频格式。
最后一个参数是模式。可能的值定义为AudioTrack类中的常量。
AudioTrack.MODE_STATIC://在播放发生之前将所有的音频数据转移到AudioTrack对象 AudioTrack.MODE_STREAM://在播放的同时将音频数据持续地转移到AudioTrack对象。
AudioTrack audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, bufferSize, AudioTrack.MODE_STREAM);
构造了AudioTrack对象之后,就需要打开音频源,将音频数据读取到缓冲区中,并将它传递给AudioTrack对象。我们可以根据一个包含了正确格式的原始PCM数据的文件,构造DataInputStream。
DataInputStream dis = new DataInputStream( new BufferedInputStream(new FileInputStream( recordingFile)));然后调用AudioTrack对象上的play方法,并开始从DataInputStream读取音频。
audioTrack.play(); while (isPlaying && dis.available() > 0) { int i = 0; while (dis.available() > 0 && i < audiodata.length) { audiodata[i] = dis.readShort(); i++; } audioTrack.write(audiodata, 0, audiodata.length); } dis.close();下面是一个完整的示例,通过使用异步任务,每个操作都在它们各自的线程中完成,所以并不会阻碍UI线程。
public class AltAudioRecorder extends Activity implements OnClickListener { RecordAudio recordTask; PlayAudio playTask; Button startRecordingButton, stopRecordingButton, startPlaybackButton, stopPlaybackButton; TextView statusText; File recordingFile; boolean isRecording = false; boolean isPlaying = false; int frequency = 11025; int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO; int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); statusText = (TextView) this.findViewById(R.id.StatusTextView); startRecordingButton = (Button) this .findViewById(R.id.StartRecordingButton); stopRecordingButton = (Button) this .findViewById(R.id.StopRecordingButton); startPlaybackButton = (Button) this .findViewById(R.id.StartPlaybackButton); stopPlaybackButton = (Button) this .findViewById(R.id.StopPlaybackButton); startRecordingButton.setOnClickListener(this); stopRecordingButton.setOnClickListener(this); startPlaybackButton.setOnClickListener(this); stopPlaybackButton.setOnClickListener(this); stopRecordingButton.setEnabled(false); startPlaybackButton.setEnabled(false); stopPlaybackButton.setEnabled(false); File path = new File( Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/AudioRecorder/files/"); path.mkdirs(); try { recordingFile = File.createTempFile("recording", ".pcm", path); } catch (IOException e) { throw new RuntimeException("Couldn't create file on SD card", e); } } public void onClick(View v) { if (v == startRecordingButton) { record(); } else if (v == stopRecordingButton) { stopRecording(); } else if (v == startPlaybackButton) { play(); } else if (v == stopPlaybackButton) { stopPlaying(); } } public void play() { startPlaybackButton.setEnabled(true); playTask = new PlayAudio(); playTask.execute(); stopPlaybackButton.setEnabled(true); } public void stopPlaying() { isPlaying = false; stopPlaybackButton.setEnabled(false); startPlaybackButton.setEnabled(true); } public void record() { startRecordingButton.setEnabled(false); stopRecordingButton.setEnabled(true); // For Fun startPlaybackButton.setEnabled(true); recordTask = new RecordAudio(); recordTask.execute(); } public void stopRecording() { isRecording = false; } private class PlayAudio extends AsyncTask<Void, Integer, Void> { @Override protected Void doInBackground(Void... params) { isPlaying = true; int bufferSize = AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding); short[] audiodata = new short[bufferSize / 4]; try { DataInputStream dis = new DataInputStream( new BufferedInputStream(new FileInputStream( recordingFile))); AudioTrack audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, bufferSize, AudioTrack.MODE_STREAM); audioTrack.play(); while (isPlaying && dis.available() > 0) { int i = 0; while (dis.available() > 0 && i < audiodata.length) { audiodata[i] = dis.readShort(); i++; } audioTrack.write(audiodata, 0, audiodata.length); } dis.close(); startPlaybackButton.setEnabled(false); stopPlaybackButton.setEnabled(true); } catch (Throwable t) { Log.e("AudioTrack", "Playback Failed"); } return null; } } private class RecordAudio extends AsyncTask<Void, Integer, Void> { @Override protected Void doInBackground(Void... params) { isRecording = true; try { DataOutputStream dos = new DataOutputStream( new BufferedOutputStream(new FileOutputStream( recordingFile))); int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize); short[] buffer = new short[bufferSize]; audioRecord.startRecording(); int r = 0; while (isRecording) { int bufferReadResult = audioRecord.read(buffer, 0, bufferSize); for (int i = 0; i < bufferReadResult; i++) { dos.writeShort(buffer[i]); } publishProgress(new Integer(r)); r++; } audioRecord.stop(); dos.close(); } catch (Throwable t) { Log.e("AudioRecord", "Recording Failed"); } return null; } protected void onProgressUpdate(Integer... progress) { statusText.setText(progress[0].toString()); } protected void onPostExecute(Void result) { startRecordingButton.setEnabled(true); stopRecordingButton.setEnabled(false); startPlaybackButton.setEnabled(true); } } }