AudioRecord和AudioTrack类是Android获取和播放音频流的重要类,放置在android.media包中。与该包中 的MediaRecorder和MediaPlayer类不同,AudioRecord和AudioTrack类在获取和播放音频数据流时无需通过文件保 存和文件读取,可以动态地直接获取和播放音频流,在实时处理音频数据流时非常有用。

当然,如果用户只想录音后写入文件或从文件中取得音频流进行播放,那么直接使用MediaRecorder和MediaPlayer类是首选方案,因为这 两个类使用非常方便,而且成功率很高。而AudioRecord和AudioTrack类的使用却比较复杂,我们发现很多人都不能成功地使用这两个类,甚 至认为Android的这两个类是不能工作的。

其实,AudioRecord和AudioTrack类的使用虽然比较复杂,但是可以工作,我们不仅可以很好地使用了这两个类,而且还通过套接字 (Socket)实现了音频数据的网络传输,做到了一端使用AudioRecord获取音频流然后通过套接字传输出去,而另一端通过套接字接收后使用 AudioTrack类播放。

下面是我们对AudioRecord和AudioTrack类在使用方面的经验总结:

(1)创建AudioRecord和AudioTrack类对象:创建这两个类的对象比较复杂,通过对文档的反复和仔细理解,并通过多次失败的尝试,并在 北理工的某个Android大牛的网上的文章启发下,我们也最终成功地创建了这两个类的对象。

创建AudioRecord和AudioTrack类对象的 代码如下:

AudioRecord类:

         m_in_buf_size =AudioRecord.getMinBufferSize(8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT); m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,
8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
m_in_buf_size) ;
AudioTrack类:

         m_out_buf_size = android.media.AudioTrack.getMinBufferSize(8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT); m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
m_out_buf_size,
AudioTrack.MODE_STREAM);

(2)关于AudioRecord和AudioTrack类的监听函数,不用也行。

(3)调试方面,包括初始化后看logcat信息,以确定类的工作状态,初始化是否成功等。

编写好代码,没有语法错误,调用模拟器运行、调试代码时,logcat发挥了很好的功用。刚调试时,经常会出现模拟器显示出现异常,这时我们可以在代码的 一些关键语句后添加如Log.d("test1","OK");这样的语句进行标识,出现异常时我们就可以在logcat窗口观察代码执行到哪里出现异 常,然后进行相应的修改、调试。模拟器不会出现异常时,又遇到了录放音的问题。录音方面,刚开始选择将语音编码数据存放在多个固定大小的文件中进行传送, 但是这种情况下会出现声音断续的现象,而且要反复的建立文件,比较麻烦,后来想到要进行网上传输,直接将语音编码数据以数据流的形式传送,经过验证,这种 方法可行并且使代码更加简洁。放音方面,将接收到的数据流存放在一个数组中,然后将数组中数据写到AudioTrack中。刚开始只是“嘟”几声,经过检 查发现只是把数据写一次,加入循环,让数据反复写到AudioTrack中,就可以听到正常的语音了。接下来的工作主要是改善话音质量与话音延迟,在进行 通话的过程中,观察logcat窗口,发现向数组中写数据时会出现Bufferflow的情况,于是把重心转移到数组大小的影响上,经过试验,发现 AudioRecord一次会读640个数据,然后就对录音和放音中有数组的地方进行实验修改。AudioRecord和AudioTrack进行实例化 时,参数中各有一个数组大小,经过试验这个数组大小和AudioRecord和AudioTrack能正常实例化所需的最小Buffer大小(即上面实例 化时的m_in_buf_size和m_out_buf_size参数)相等且服务器方进行缓存数据的数组尺寸是上述数值的2倍时,语音质量最好。由于录 音和放音的速度不一致,受到北理工大牛的启发,在录音方面,将存放录音数据的数组放到LinkedList中,当LinkedList中数组个数达到 2(这个也是经过试验验证话音质量最好时的数据)时,将先录好的数组中数据传送出去。经过上述反复试验和修改,最终使双方通话质量较好,且延时较短(大概 有2秒钟)。

(4)通过套接字传输和接收数据

数据传送部分,使用的是套接字。通信双方,通过不同的端口向服务器发送请求,与服务器连接上后,开始通话向服务器发送数据,服务器通过一个套接字接收到一 方的数据后,先存在一个数组中,然后将该数组中数据以数据流的形式再通过另一个套接字传送到另一方。这样就实现了双方数据的传送。

(5)代码架构

为避免反复录入和读取数据占用较多资源,使程序在进行录放音时不能执行其他命令,故将录音和放音各写成一个线程类,然后在主程序中,通过MENU控制通话的开始、停止、结束。

最后说明,AudioRecord和AudioTrack类可以用,只是稍微复杂些。以下贴出双方通信的源码,希望对大家有所帮助:

主程序Daudioclient:

package cn.Daudioclient;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem; public class Daudioclient extends Activity { public static final int MENU_START_ID = Menu.FIRST ;
public static final int MENU_STOP_ID = Menu.FIRST + 1 ;
public static final int MENU_EXIT_ID = Menu.FIRST + 2 ; protected Saudioserver m_player ;
protected Saudioclient m_recorder ; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
} public boolean onCreateOptionsMenu(Menu aMenu)
{
boolean res = super.onCreateOptionsMenu(aMenu) ; aMenu.add(0, MENU_START_ID, 0, "START") ;
aMenu.add(0, MENU_STOP_ID, 0, "STOP") ;
aMenu.add(0, MENU_EXIT_ID, 0, "EXIT") ; return res ;
} public boolean onOptionsItemSelected(MenuItem aMenuItem)
{
switch (aMenuItem.getItemId()) {
case MENU_START_ID:
{
m_player = new Saudioserver() ;
m_recorder = new Saudioclient() ; m_player.init() ;
m_recorder.init() ; m_recorder.start() ;
m_player.start() ; }
break ;
case MENU_STOP_ID:
{
m_recorder.free() ;
m_player.free() ; m_player = null ;
m_recorder = null ;
}
break ;
case MENU_EXIT_ID:
{
int pid = android.os.Process.myPid() ;
android.os.Process.killProcess(pid) ;
}
break ;
default:
break ;
} return super.onOptionsItemSelected(aMenuItem);
}
}
录音程序Saudioclient:

package cn.Daudioclient;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList; import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log; public class Saudioclient extends Thread
{ protected AudioRecord m_in_rec ;
protected int m_in_buf_size ;
protected byte [] m_in_bytes ;
protected boolean m_keep_running ;
protected Socket s;
protected DataOutputStream dout;
protected LinkedList<byte[]> m_in_q ; public void run()
{
try
{
byte [] bytes_pkg ;
m_in_rec.startRecording() ;
while(m_keep_running)
{
m_in_rec.read(m_in_bytes, 0, m_in_buf_size) ;
bytes_pkg = m_in_bytes.clone() ;
if(m_in_q.size() >= 2)
{
dout.write(m_in_q.removeFirst() , 0, m_in_q.removeFirst() .length);
}
m_in_q.add(bytes_pkg) ;
} m_in_rec.stop() ;
m_in_rec = null ;
m_in_bytes = null ;
dout.close(); }
catch(Exception e)
{
e.printStackTrace();
}
} public void init()
{
m_in_buf_size = AudioRecord.getMinBufferSize(8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT); m_in_rec = new AudioRecord(MediaRecorder.AudioSource.MIC,
8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
m_in_buf_size) ; m_in_bytes = new byte [m_in_buf_size] ; m_keep_running = true ;
m_in_q=new LinkedList<byte[]>(); try
{
s=new Socket("192.168.1.100",4332);
dout=new DataOutputStream(s.getOutputStream());
//new Thread(R1).start();
}
catch (UnknownHostException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
} } public void free()
{
m_keep_running = false ;
try {
Thread.sleep(1000) ;
} catch(Exception e) {
Log.d("sleep exceptions...\n","") ;
}
}
}
放音程序Saudioserver:

package cn.Daudioclient;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket; import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log; public class Saudioserver extends Thread
{
protected AudioTrack m_out_trk ;
protected int m_out_buf_size ;
protected byte [] m_out_bytes ;
protected boolean m_keep_running ;
private Socket s;
private DataInputStream din;
public void init()
{
try
{
s=new Socket("192.168.1.100",4331);
din=new DataInputStream(s.getInputStream()); m_keep_running = true ; m_out_buf_size = AudioTrack.getMinBufferSize(8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT); m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
m_out_buf_size,
AudioTrack.MODE_STREAM); m_out_bytes=new byte[m_out_buf_size]; // new Thread(R1).start(); }
catch(Exception e)
{
e.printStackTrace();
}
} public void free()
{
m_keep_running = false ;
try {
Thread.sleep(1000) ;
} catch(Exception e) {
Log.d("sleep exceptions...\n","") ;
}
} public void run()
{
byte [] bytes_pkg = null ;
m_out_trk.play() ;
while(m_keep_running) {
try
{
din.read(m_out_bytes);
bytes_pkg = m_out_bytes.clone() ;
m_out_trk.write(bytes_pkg, 0, bytes_pkg.length) ;
}
catch(Exception e)
{
e.printStackTrace();
} } m_out_trk.stop() ;
m_out_trk = null ;
try {
din.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
AudioRecord

结构

继承关系

public class AudioRecord extends Object

  java.lang.Object

android.media.AudioRecord

类概述

  AudioRecord类在Java应用程序中管理音频资源,用来记录从平台音频输入设备产生的数据。 通过AudioRecord对象来完成"pulling"(读取)数据。 应用通过以下几个方法负责立即从AudioRecord对象读取: read(byte[], int, int), read(short[], int, int)或read(ByteBuffer, int). 无论使用哪种音频格式,使用AudioRecord是最方便的。

  在创建AudioRecord对象时,AudioRecord会初始化,并和音频缓冲区连接,用来缓冲新的音频数据。 根据构造时指定的缓冲区大小,来决定AudioRecord能够记录多长的数据。 从硬件设备读取的数据,应小于整个记录缓冲区。

内部类

interface          AudioRecord.OnRecordPositionUpdateListener

  接口定义为:当AudioRecord 收到一个由setNotificationMarkerPosition(int)设置的通知标志,或由 setPositionNotificationPeriod(int)设置的周期更新记录的进度状态时,回调此接口。

常量

    public static final int ERROR

  表示操作失败。

                   常量值: -1 (0xffffffff)

  public static final int ERROR_BAD_VALUE

  表示使用了一个不合理的值导致的失败。

        常量值: -2 (0xfffffffe)

  public static final int ERROR_INVALID_OPERATION

  表示不恰当的方法导致的失败。

        常量值: -3 (0xfffffffd)

  public static final int RECORDSTATE_RECORDING

  指示AudioRecord录制状态为“正在录制”。

        常量值: 3 (0x00000003)

  public static final int RECORDSTATE_STOPPED

  指示AudioRecord录制状态为“不在录制”。

        常量值: 1 (0x00000001)

  public static final int STATE_INITIALIZED

  指示AudioRecord准备就绪。

        常量值: 1 (0x00000001)

  public static final int STATE_UNINITIALIZED

  指示AudioRecord状态没有初始化成功。

        常量值: 0 (0x00000000)

  public static final int SUCCESS

  表示操作成功。

        常量值: 0 (0x00000000)

构造函数

  public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)

  类构造函数。

         参数

                   audioSource     录制源。 请见MediaRecorder.AudioSource录制源定义。

sampleRateInHz      默认采样率,单位Hz。 44100Hz是当前唯一能保证在所有设备上工作的采样率,在一些设备上还有22050, 16000或11025。

channelConfig          描述音频通道设置。 请见CHANNEL_IN_MONO 和 CHANNEL_IN_STEREO。 CHANNEL_IN_MONO保证能在所有设备上工作。

audioFormat             音频数据保证支持此格式。 请见ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。

bufferSizeInBytes    在录制过程中,音频数据写入缓冲区的总数(字节)。 从缓冲区读取的新音频数据总会小于此值。 getMinBufferSize(int, int, int)返回AudioRecord 实例创建成功后的最小缓冲区。 设置的值比getMinBufferSize()还小则会导致初始化失败。

                   异常

                            IllegalArgumentException

公共方法

    public int getAudioFormat ()

    返回设置的音频数据格式。 请见ENCODING_PCM_16BIT 和ENCODING_PCM_8BIT。

    public int getAudioSource ()

    返回音频录制源。

      参见

                            MediaRecorder.AudioSource

    public int getChannelConfiguration ()

    返回设置的频道设置。 请见CHANNEL_IN_MONO和CHANNEL_IN_STEREO。

    public int getChannelCount ()

    返回设置的频道数目。

    public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)

    返回成功创建AudioRecord对象所需要的最小缓冲区大小。 注意:这个大小并不保证在负荷下的流畅录制,应根据预期的频率来选择更高的值,AudioRecord实例在推送新数据时使用此值。

      参数

                            sampleRateInHz      默认采样率,单位Hz。

                            channelConfig           描述音频通道设置。

请见CHANNEL_IN_MONO和CHANNEL_IN_STEREO。

                            audioFormat             音频数据保证支持此格式。参见ENCODING_PCM_16BIT。

      返回值

  如果硬件不支持录制参数,或输入了一个无效的参数,则返回ERROR_BAD_VALUE,如果硬件查询到输出属性没有实现,或最小缓冲区用byte表示,则返回ERROR。

      参见

                            更多信息请见有效的设置参数

    public int getNotificationMarkerPosition ()

    返回通知,标记框架中的位置。

    public int getPositionNotificationPeriod ()

    返回通知,更新框架中的时间位置。

    public int getRecordingState ()

    返回AudioRecord实例的录制状态。

      参见

                       RECORDSTATE_STOPPED

             RECORDSTATE_RECORDING

    public int getSampleRate ()

    返回设置的音频数据样本采样率,单位Hz。

    public int getState ()

    返回AudioRecord实例的状态。 这点非常有用,用在AudioRecord 实例创建成功后,检查初始化属性。 它能肯定请求到了合适的硬件资源。

      参见

                   STATE_INITIALIZED

             STATE_UNINITIALIZED

    public int read (short[] audioData, int offsetInShorts, int sizeInShorts)

    从音频硬件录制缓冲区读取数据。

             参数

                            audioData        写入的音频录制数据。

                            offsetInShorts           目标数组 audioData 的起始偏移量。

                            sizeInShorts              请求读取的数据大小。

      返回值

  返回short型数据,表示读取到的数据,如果对象属性没有初始化,则返回ERROR_INVALID_OPERATION,如果参数不能解析成有效的数据或索引,则返回ERROR_BAD_VALUE。 返回数值不会超过sizeInShorts。

    public int read (byte[] audioData, int offsetInBytes, int sizeInBytes)

    从音频硬件录制缓冲区读取数据。

                   参数

                            audioData        写入的音频录制数据。

                            offsetInBytes            audioData的起始偏移值,单位byte。

                            sizeInBytes                读取的最大字节数。

                   返回值

  读入缓冲区的总byte数,如果对象属性没有初始化,则返回ERROR_INVALID_OPERATION,如果参数不能解析成有效的数据或索引,则返回ERROR_BAD_VALUE。 读取的总byte数不会超过sizeInBytes。

    public int read (ByteBuffer audioBuffer, int sizeInBytes)

    从音频硬件录制缓冲区读取数据,直接复制到指定缓冲区。 如果audioBuffer不是直接的缓冲区,此方法总是返回0。

                   参数

                            audioBuffer               存储写入音频录制数据的缓冲区。

                            sizeInBytes                请求的最大字节数。

                   返回值

  读入缓冲区的总byte数,如果对象属性没有初始化,则返回ERROR_INVALID_OPERATION,如果参数不能解析成有效的数据或索引,则返回ERROR_BAD_VALUE。 读取的总byte数不会超过sizeInBytes。

    public void release ()

    释放本地AudioRecord资源。 对象不能经常使用此方法,而且在调用release()后,必须设置引用为null。

    public int setNotificationMarkerPosition (int markerInFrames)

    如果设置了 setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或 setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),则通知监听者设置位置标记。

                   参数

                            markerInFrames      在框架中快速标记位置。

                   返回值

                            返回错误或成功代码,请见SUCCESS、ERROR_BAD_VALUE、ERROR_INVALID_OPERATION。

    public int setPositionNotificationPeriod (int periodInFrames)

    如果设置了 setRecordPositionUpdateListener(OnRecordPositionUpdateListener)或 setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler),则通知监听者设置时间标记。

                   参数

                            markerInFrames      在框架中快速更新时间标记。

                   返回值

                            返回错误或成功代码,请见SUCCESS、ERROR_INVALID_OPERATION。

   public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener, Handler handler)

  当之前设置的标志已经成立,或者周期录制位置更新时,设置处理监听者。 使用此方法来将Handler 和别的线程联系起来,来接收AudioRecord 事件,比创建AudioTrack 实例更好一些。

  参数

              handler    用来接收事件通知消息。

   public void setRecordPositionUpdateListener (AudioRecord.OnRecordPositionUpdateListener listener)

    当之前设置的标志已经成立,或者周期录制位置更新时,设置处理监听者。

    public void startRecording ()

    AudioRecord实例开始进行录制。

                   异常

                            IllegalStateException

受保护方法

  protected void finalize ()

    通知VM回收此对象内存。 此方法只能用在运行的应用程序没有任何线程再使用此对象,来告诉垃圾回收器回收此对象。

  此方法用于释放系统资源,由垃圾回收器清除此对象。 默认没有实现,由VM来决定,但子类根据需要可重写finalize()。 在执行期间,调用此方法可能会立即抛出未定义异常,但是可以忽略。

  注意:VM保证对象可以一次或多次调用finalize(),但并不保证finalize()会马上执行。 例如,对象B的finalize()可能延迟执行,等待对象A的finalize()延迟回收A的内存。 为了安全起见,请看ReferenceQueue,它提供了更多地控制VM的垃圾回收。

android通过数组,流播放声音的方法,音频实时传输的更多相关文章

  1. JAVA IO分析二:字节数组流、基本数据&对象类型的数据流、打印流

    上一节,我们分析了常见的节点流(FileInputStream/FileOutputStream  FileReader/FileWrite)和常见的处理流(BufferedInputStream/B ...

  2. Android Multimedia框架总结(十七)音频开发基础知识

    请尊重分享成果,转载请注明出处,本文来自逆流的鱼yuiop,原文链接:http://blog.csdn.net/hejjunlin/article/details/53078828 近年来,唱吧,全民 ...

  3. [源码]ObjectIOStream 对象流 ByteArrayIOStream 数组流 内存流 ZipOutputStream 压缩流

    1.对象流 import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File ...

  4. opencv直线检测在c#、Android和ios下的实现方法

    opencv直线检测在c#.Android和ios下的实现方法 本文为作者原创,未经允许,不得转载 :原文由作者发表在博客园:http://www.cnblogs.com/panxiaochun/p/ ...

  5. Java:IO流其他类(字节数组流、字符数组流、数据流、打印流、Properities、对象流、管道流、随机访问、序列流、字符串读写流)

    一.字节数组流: 类 ByteArrayInputStream:在构造函数的时候,需要接受数据源,而且数据源是一个字节数组. 包含一个内部缓冲区,该缓冲区包含从流中读取的字节.内部计数器跟踪 read ...

  6. Java IO学习笔记(三)转换流、数据流、字节数组流

    转换流 1.转换流:将字节流转换成字符流,转换之后就可以一个字符一个字符的往程序写内容了,并且可以调用字符节点流的write(String s)方法,还可以在外面套用BufferedReader()和 ...

  7. java字符流操作flush()方法及其注意事项

    java字符流操作flush()方法及其注意事项   flush()方法介绍 查阅文档可以发现,IO流中每一个类都实现了Closeable接口,它们进行资源操作之后都需要执行close()方法将流关闭 ...

  8. IO知识点整理(序列化,管道流,数据流,字节数组流,与编码)

    一:序列化的问题 1.序列号的使用问题 关于在序列化中的序列号的使用问题,一般要是使用. 因为,每次要序列化的类产生都会产生一个一个新的序列号,如果将这个类的程序修改后,就会产生新的序列号,以前序列化 ...

  9. Android Activity启动流程源码全解析(1)

    前言 Activity是Android四大组件的老大,我们对它的生命周期方法调用顺序都烂熟于心了,可是这些生命周期方法到底是怎么调用的呢?在启动它的时候会用到startActivty这个方法,但是这个 ...

随机推荐

  1. Html5 小球键盘移动

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <t ...

  2. 关于Cocos2d-x中定时器的使用总结

    1.定义 定时器在大部分游戏中是不可或缺的,即每隔一段时间,就要执行相应的刷新体函数,以更新游戏的画面.时间.进度.敌人的指令等等. cocos2dx为我们提供了定时器schedule相关的操作.其操 ...

  3. ioctl参数cmd=2错误

    在写内核驱动的时候,用到了ioctl.自己定义cmd作为ioctl的参数.如下: enum CMD { LEVEL_DOWN, LEVEL_UP, GPIO_INPUT, GPIO_OUTPUT, G ...

  4. 利用Spring AOP和自定义注解实现日志功能

    Spring AOP的主要功能相信大家都知道,日志记录.权限校验等等. 用法就是定义一个切入点(Pointcut),定义一个通知(Advice),然后设置通知在该切入点上执行的方式(前置.后置.环绕等 ...

  5. 第三百零八节,Django框架,models.py模块,数据库操作——链表结构,一对多、一对一、多对多

    第三百零八节,Django框架,models.py模块,数据库操作——链表结构,一对多.一对一.多对多 链表操作 链表,就是一张表的外键字段,连接另外一张表的主键字段 一对多 models.Forei ...

  6. 编写一个Filter,除继承HttpServlet类外还需要( )。

    A.继承Filter 类 B.实现Filter 接口 C.继承HttpFilter 类 D.实现HttpFilter接口 解答:B

  7. IoC就是IoC,不是什么技术,与GoF一样,是一种 设计模式。

    IoC就是IoC,不是什么技术,与GoF一样,是一种 设计模式. Interface Driven Design接口驱动,接口驱动有很多好处,可以提供不同灵活的子类实现,增加代码稳定和健壮性等等,但是 ...

  8. goto 语句

    goto 语句 自从提倡结构化设计以来,goto 就成了有争议的语句. 首先,由于 goto 语句可以 灵活跳转,如果不加限制,它的确会破坏结构化设计风格.其次,goto 语句经常带来错 误或隐患. ...

  9. 终于AC了“最短路径”

    今天做了一道关于最短路径的算法题,虽然最后AC了,但是我写的第一个算法,我认为是没有问题的,自己编写的测试用例也全部通过,反复调试了,都没有错误.可是在OJ上一提交就提示Wrong Answer,真是 ...

  10. Android ContentProvider、ContentResolver和ContentObserver的使用

    1.ContentProvider.ContentResolver和ContentObserver ContentProvider是Android的四大组件之中的一个,可见它在Android中的作用非 ...