本项目是一个安卓对讲机应用的项目源码,代码规范注释清晰,功能完整界面简单。无论是做二次开发还是参考借鉴都是绝佳的项目源码。一个简单的按钮,按下就可以实时对讲,而且通过标识包头来解决自己听到自己说的话,使用方法:把AppConfig.java中的IP设为自己WiFi的网段x.x.x.0,(不必管安卓设备的具体IP,只填写设备所在IP的网段即可,例如我的手机连接wifi后IP是192.168.1.102,这里的网段就是IP的前三段+0。就是192.168.1.0),不过因为线程的启动方式有点问题所以项目有个小BUG,需要在对讲之前先按一下按钮,让线程启动,再按住按钮说话。javaapk.com测试完美运行,需要更多对讲机源码可以在例子大全 http://www.javaapk.com/demo 里搜“对讲机”就可以找到。
--------------权限-------------
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
---------------------activity_main.xml---------------------
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1D1E1D"
tools:context="com.my.zubodemo.MainActivity">
<Button
android:id="@+id/speakButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/speak_bt_select"
android:textColor="#fff"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="140dp"
android:textColor="#ff3333"
android:text="网段是第四段换成0, 例如: ip :192.168.1.245 则网段是: 192.168.1.0"/>
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:layout_marginTop="190dp"
android:background="#cccc"
android:hint="请先在这里输入网段"
/>
<TextView
android:id="@+id/Message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="28dp"
android:text="按住说话"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#fff"/>
</RelativeLayout>
----------------------speak_bt_select.xml-------------------
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/speak2" android:state_pressed="true"/>
<item android:drawable="@drawable/speak1" android:state_pressed="false"/>
</selector>
----------------------MainActivity---------------------
@SuppressLint("ClickableViewAccessibility") public class MainActivity extends AppCompatActivity { private Button speakButton;// 按住说话 private TextView message; private SendSoundsThread sendSoundsThread = new SendSoundsThread(); private ReceiveSoundsThread receiveSoundsThread = new ReceiveSoundsThread(); private boolean isFirst = true; private boolean isReceive = true; private static final String TAG = "MainActivity"; // 设备信息:手机名+Android版本 private String DevInfo = android.os.Build.MODEL + " Android " + android.os.Build.VERSION.RELEASE; private String tv; private boolean shortPress = false; private int index; private int count; private String ip; //初始化一个空数组 private int[] arry={}; private String substring; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startService(new Intent(getApplicationContext(), DaemonService.class)); Log.d("MainActivity", "onCreate(): savedInstanceState = [" + savedInstanceState + "]"); Toast.makeText(this, "开启后台守护服务", Toast.LENGTH_SHORT).show(); //finish(); //获取ip ip = getIP(MainActivity.this); int[] arr=count(ip,'.'); for (int i = 0; i <arr.length ; i++) { count = arr[i]; } substring = ip.substring(0,count+1); Log.d("哈哈", "onCreate: "+ substring); Log.d(TAG, "onCreate: -----ip:----------"+ ip); //锁屏状态Activity不销毁 //判断是否是锁屏 KeyguardManager mKeyguardManager = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE); boolean flag = mKeyguardManager.inKeyguardRestrictedInputMode(); Log.d(TAG, "onCreate: " + "-----锁屏-----" + flag); //屏幕是否亮屏 PowerManager powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE); //true为打开,false为关闭 boolean ifOpen = powerManager.isScreenOn(); Log.d(TAG, "onCreate: "+"------亮屏-----"+ifOpen); receiveSoundsThread.start(); receiveSoundsThread.setRunning(true); message = (TextView) findViewById(R.id.Message); speakButton = (Button) findViewById(R.id.speakButton); //6.0动态权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//6.0以上 int checkPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO); if (checkPermission != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{ Manifest.permission.RECORD_AUDIO }, 1); //后面的1为请求码 return; } else { speakButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { message.setText("松开结束"); if (isFirst) { sendSoundsThread.start(); isFirst = false; } sendSoundsThread.setRunning(true); receiveSoundsThread.setRunning(false); } else if (event.getAction() == MotionEvent.ACTION_UP) { message.setText("按住说话"); sendSoundsThread.setRunning(false); receiveSoundsThread.setRunning(true); } return false; } }); } } else { speakButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { message.setText("松开结束"); if (isFirst) { sendSoundsThread.start(); isFirst = false; } sendSoundsThread.setRunning(true); receiveSoundsThread.setRunning(false); } else if (event.getAction() == MotionEvent.ACTION_UP) { message.setText("按住说话"); sendSoundsThread.setRunning(false); receiveSoundsThread.setRunning(true); } return false; } }); } } //方法的定义 public int[] count(String str,char ch){ for(int i=0;i<str.length();i++){ char c=str.charAt(i); if(c==ch){ //数组长度arry.length,字符串长度str.length().Arrays.copyOf(复制的数组,数组的长度) arry= Arrays.copyOf(arry,arry.length+1); //数组的最后一个元素,arry[arry.length-1] arry[arry.length-1]=i; Log.d(TAG, "count: -----arry:------"+i); } } return arry; } /** * 按音量下键进行组播 * * @param keyCode * @param event * @return */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //scanner key if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { if (event.getAction() == KeyEvent.ACTION_DOWN) { event.startTracking(); //只有执行了这行代码才会调用onKeyLongPress if (event.getRepeatCount() == 0) { shortPress = true; //短按事件逻辑 } return true; } } if (keyCode == KeyEvent.KEYCODE_BACK) { Intent i = new Intent(Intent.ACTION_MAIN); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.addCategory(Intent.CATEGORY_HOME); startActivity(i); return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { //长按事件的逻辑 Log.d(TAG, "onKeyLongPress: " + "长按"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//6.0以上 int checkPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO); if (checkPermission != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 1);//后面的1为请求码 return true; } else { //按键弹起事件逻辑 if (shortPress) { message.setText("松开结束"); if (isFirst) { sendSoundsThread.start(); isFirst = false; } sendSoundsThread.setRunning(true); receiveSoundsThread.setRunning(false); } } } else { //按键弹起事件逻辑 if (shortPress) { message.setText("松开结束"); if (isFirst) { sendSoundsThread.start(); isFirst = false; } sendSoundsThread.setRunning(true); receiveSoundsThread.setRunning(false); } } return true; } //Just return false because the super call does always the same (returning false) return false; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) //此处是按键的对应键值 { //抬起事件的逻辑 Log.d(TAG, "onKeyLongPress: " + "抬起"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//6.0以上 int checkPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO); if (checkPermission != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 1);//后面的1为请求码 return true; } else { message.setText("按住说话"); sendSoundsThread.setRunning(false); receiveSoundsThread.setRunning(true); } } else { message.setText("按住说话"); sendSoundsThread.setRunning(false); receiveSoundsThread.setRunning(true); } } return super.onKeyUp(keyCode, event); } /** * 获取本机V4地址 * @param context * @return */ public static String getIP(Context context){ try { for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { NetworkInterface intf = en.nextElement(); for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress() && (inetAddress instanceof Inet4Address)) { return inetAddress.getHostAddress().toString(); } } } } catch (SocketException ex){ ex.printStackTrace(); } return null; } class SendSoundsThread extends Thread { private AudioRecord recorder = null; private boolean isRunning = false; private byte[] recordBytes = new byte[640]; public SendSoundsThread() { super(); // 录音机 int recordBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, recordBufferSize); } @Override public synchronized void run() { super.run(); recorder.startRecording(); while (true) { if (isRunning) { try { DatagramSocket clientSocket = new DatagramSocket(); InetAddress IP = InetAddress.getByName(substring+"0");// 向这个网络广播 // 获取音频数据 recorder.read(recordBytes, 0, recordBytes.length); // 构建数据包 头+体 dataPacket dataPacket = new dataPacket(DevInfo.getBytes(), recordBytes); // 构建数据报 DatagramPacket sendPacket = new DatagramPacket(dataPacket.getAllData(), dataPacket.getAllData().length, IP, AppConfig.Port); // 发送 clientSocket.send(sendPacket); clientSocket.close(); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } } private DatagramPacket receivePacket; class ReceiveSoundsThread extends Thread { private AudioTrack player = null; private boolean isRunning = false; private byte[] recordBytes = new byte[670]; public ReceiveSoundsThread() { // 播放器 int playerBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); player = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, playerBufferSize, AudioTrack.MODE_STREAM); } @Override public synchronized void run() { super.run(); try { @SuppressWarnings("resource") DatagramSocket serverSocket = new DatagramSocket(AppConfig.Port); while (true) { if (isRunning) { receivePacket = new DatagramPacket(recordBytes, recordBytes.length); serverSocket.receive(receivePacket); byte[] data = receivePacket.getData(); byte[] head = new byte[30]; byte[] body = new byte[640]; // 获得包头 for (int i = 0; i < head.length; i++) { head[i] = data[i]; } // 获得包体 for (int i = 0; i < body.length; i++) { body[i] = data[i + 30]; } // 获得头信息 通过头信息判断是否是自己发出的语音 String thisDevInfo = new String(head).trim(); System.out.println(thisDevInfo); if (!thisDevInfo.equals(DevInfo)) { player.write(body, 0, body.length); player.play(); } } } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } } @Override protected void onDestroy() { super.onDestroy(); android.os.Process.killProcess(android.os.Process.myPid()); } }
----------------------------AppConfig ---------------------
public class AppConfig {
// public static String IPAddress = "192.168.30.0";// 局域网
public static int Port = 8000;
}
---------------------dataPacket--------------------
public class dataPacket {
public static int head = 30;// 头信息大小
public static int body = 640;// 语音包大小
private byte[] recordBytes = new byte[head + body];
public dataPacket(byte[] headInfo, byte[] bodyBytes)
{
for (int i = 0; i < headInfo.length; i++)
{
recordBytes[i] = headInfo[i];
}
for (int i = 0; i < bodyBytes.length; i++)
{
recordBytes[i + 30] = bodyBytes[i];
}
}
public byte[] getHeadInfo()
{
byte[] head = new byte[30];
for (int i = 0; i < head.length; i++)
{
head[i] = recordBytes[i];
}
return head;
}
public byte[] getBody()
{
byte[] body = new byte[640];
for (int i = 0; i < body.length; i++)
{
body[i] = recordBytes[i + 30];
}
return body;
}
public byte[] getAllData()
{
byte[] data = new byte[head + body];
for (int i = 0; i < data.length; i++)
{
data[i] = recordBytes[i];
}
return data;
}
}