文章的目的为了记录使用java 进行android app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 java android app 开发(一)开发环境的搭建-优快云博客
开源 java android app 开发(二)工程文件结构-优快云博客
开源 java android app 开发(三)GUI界面布局和常用组件-优快云博客
开源 java android app 开发(四)GUI界面重要组件-优快云博客
开源 java android app 开发(五)文件和数据库存储-优快云博客
开源 java android app 开发(六)多媒体使用-优快云博客
开源 java android app 开发(七)通讯之Tcp和Http-优快云博客
开源 java android app 开发(八)通讯之Mqtt和Ble-优快云博客
推荐链接:
开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-优快云博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-优快云博客
开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-优快云博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-优快云博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-优快云博客
本章节主要内容是安卓通讯协议的使用,通讯内容太多分为2章,本章内容为Mqtt和Ble
本章内容如下:
1.Mqtt协议通讯
2.Ble协议通讯
一、Mqtt协议通讯
MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布订阅范式的消息协议。它工作在 TCP/IP协议之上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个消息中间件 。
代码实现了连接mqtt服务器,实现按钮发送消息,接收消息后显示到editview。这里需要注意的是需要自建1个Mqtt服务器和另1个Mqtt的客户端才方便验证,服务器采用了Emqx,另1个客户端使用了MQTT.fx,具体使用内容太长此处不进行说明。
1.1.AndroidManifest.xml添加权限
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
1.2 添加依赖,位于src文件夹下的build.gradle文件中
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
1.3 Activity_main.xml界面代码
?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView" />
<EditText
android:id="@+id/editText2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="textPersonName"
android:text="Name" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button" />
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
1.4 添加Mqtt服务类MqttService.java
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
public class MqttService extends Service{
public static final String TAG = MqttService.class.getSimpleName();
private static String host = "tcp://192.168.3.131:1883";
private static String userName = "test";
private static String passWord = "123456";
private static String sendTopic = "test"; //要发布的主题
private static String recTopic[] = {"recv1","recv2","recv3"}; //要订阅的主题
//private static String recTopic ="null/null/Gateway/00:09:C0:FF:EC:48/05";
private static String clientId = "androidId";//客户端标识
private IGetMessageCallBack IGetMessageCallBack;
private static MqttAndroidClient client;
private MqttConnectOptions conOpt;
@Override
public void onCreate() {
super.onCreate();
Log.e(getClass().getName(), "onCreate");
init();
}
// 主题是myTopic 消息质量等级 0 消息在服务器端不保存
public static void publish(String msg){
String topic = MqttService.sendTopic;
try {
if (client != null){
client.publish(topic, msg.getBytes(),0, false,null,null);
Log.i("Publish","pubish mes is"+" "+ msg);
}
} catch (MqttException e) {
e.printStackTrace();
}
}
private void init() {
// 服务器地址(协议+地址+端口号)
String uri = host;
client = new MqttAndroidClient(this, uri, clientId);
// 设置MQTT监听并且接受消息 Sets a callback listener to use for events that happen asynchronously.
client.setCallback(mqttCallback);
conOpt = new MqttConnectOptions();
// 清除缓存
conOpt.setCleanSession(true);
// 设置超时时间,单位:秒
conOpt.setConnectionTimeout(10);
// 心跳包发送间隔,单位:秒
conOpt.setKeepAliveInterval(20);
// 用户名
conOpt.setUserName(userName);
// 密码
conOpt.setPassword(passWord.toCharArray()); //将字符串转换为字符串数组
// last will message
boolean doConnect = true;
String message = "{\"terminal_uid\":\"" + clientId + "\"}";
Log.e(getClass().getName(), "message是:" + message);
String topic = sendTopic;
Integer qos = 0;
Boolean retained = false;
if ((!message.equals("")) || (!topic.equals(""))) {
// 最后的遗嘱
// MQTT本身就是为信号不稳定的网络设计的,所以难免一些客户端会无故的和Broker断开连接。
//当客户端连接到Broker时,可以指定LWT,Broker会定期检测客户端是否有异常。
//当客户端异常掉线时,Broker就往连接时指定的topic里推送当时指定的LWT消息。
try {
conOpt.setWill(topic, message.getBytes(), qos.intValue(), retained.booleanValue());
} catch (Exception e) {
Log.i(TAG, "Exception Occured", e);
doConnect = false;
iMqttActionListener.onFailure(null, e);
}
}
if (doConnect) {
//连接Mqtt服务器
doClientConnection();
}
}
@Override
public void onDestroy() {
stopSelf();
try {
client.disconnect();
} catch (MqttException e) {
e.printStackTrace();
}
super.onDestroy();
}
/** 连接MQTT服务器 */
private void doClientConnection() {
//是否连接服务器 网络连接是否正常 连接成功后订阅主题
if (!client.isConnected() && isConnectIsNormal()) {
try {
client.connect(conOpt, null, iMqttActionListener);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
// MQTT是否连接成功
private IMqttActionListener iMqttActionListener = new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken arg0) {
Log.i(TAG, "连接成功 ");
try {
// 订阅myTopic话题
for(int i=0;i<3;i++)
client.subscribe(recTopic[i],1);
} catch (MqttException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(IMqttToken arg0, Throwable arg1) {
arg1.printStackTrace();
// 连接失败,重连
}
};
// MQTT监听并且接受消息
private MqttCallback mqttCallback = new MqttCallback() {
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String str1 = new String(message.getPayload());
if (IGetMessageCallBack != null){
//光敏
if(topic.equals(recTopic[0])){
IGetMessageCallBack.setMessage(str1+"lx");//调用activity中的setMessage();
}
//温度
else if(topic.equals(recTopic[1])){
IGetMessageCallBack.setMessage1(str1+"℃");//调用activity中的setMessage();
}
//湿度
else if(topic.equals(recTopic[2])){
IGetMessageCallBack.setMessage2(str1+"%");//调用activity中的setMessage();
}
//IGetMessageCallBack.setMessage(str1);//调用activity中的setMessage();
}
String str2 = topic + ";qos:" + message.getQos() + ";retained:" + message.isRetained();
Log.i(TAG, "messageArrived:" + str1);
Log.i(TAG, str2);
}
@Override
public void deliveryComplete(IMqttDeliveryToken arg0) {
}
@Override
public void connectionLost(Throwable arg0) {
// 失去连接,重连
}
};
/** 判断网络是否连接 */
private boolean isConnectIsNormal() {
ConnectivityManager connectivityManager = (ConnectivityManager) this.getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable()) {
String name = info.getTypeName();
Log.i(TAG, "MQTT当前网络名称:" + name);
return true;
} else {
Log.i(TAG, "MQTT 没有可用网络");
return false;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getName(), "onBind");
return new CustomBinder();
}
public void setIGetMessageCallBack(IGetMessageCallBack IGetMessageCallBack){
this.IGetMessageCallBack = IGetMessageCallBack;
}
public class CustomBinder extends Binder {
//公开方法给activity调用
public MqttService getService(){
return MqttService.this;
}
}
}
1.5 添加mqtt连接类MyServiceConnection.java
public class MyServiceConnection implements ServiceConnection {
private MqttService mqttService;
private IGetMessageCallBack IGetMessageCallBack;
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mqttService = ((MqttService.CustomBinder)iBinder).getService();
mqttService.setIGetMessageCallBack(IGetMessageCallBack);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
public MqttService getMqttService(){
return mqttService;
}
public void setIGetMessageCallBack(IGetMessageCallBack IGetMessageCallBack){
this.IGetMessageCallBack = IGetMessageCallBack;
}
}
1.6 添加接口IGetMessageCallBack.java
public interface IGetMessageCallBack {
public void setMessage(String message);
public void setMessage1(String message);
public void setMessage2(String message);
}
1.7 修改MainActivity.java代码
public class MainActivity extends AppCompatActivity implements IGetMessageCallBack {
// private TextView txt3,txt4,txt5;
private String str[]={"","",""};
private SimpleAdapter adapter;
private EditText txt;
private Button button;
private ArrayList list,newlist;
private ListView listView;
private MyServiceConnection serviceConnection;
int j = 0;
// private MqttService mqttService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// txt3 = findViewById(R.id.txt3);
// txt4 = findViewById(R.id.txt4);
// txt5 = findViewById(R.id.txt5);
button = findViewById(R.id.button);
txt = findViewById(R.id.editText2);
//initDataList();
// key值数组,适配器通过key值取value,与列表项组件一一对应
String[] from = { "img", "title", "content" };
// 列表项组件Id 数组
/**
* SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
* context:activity界面类
* data 数组内容是map的集合数据
* resource 列表项文件
* from map key值数组
* to 列表项组件id数组 from与to一一对应,适配器绑定数据
*/
/*
adapter = new SimpleAdapter(this, list,
R.layout.listview_item, from, to);
listView.setAdapter(adapter);
*/
serviceConnection = new MyServiceConnection();
//MyServiceConnection类中定义的方法setIGetMessageCallBack
serviceConnection.setIGetMessageCallBack(MainActivity.this);
Intent intent = new Intent(this, MqttService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
j++;
if (j == 1) {
MqttService.publish("0");
//button.setBackgroundResource(R.mipmap.light3);
} else if (j == 2) {
MqttService.publish("1");
//button.setBackgroundResource(R.mipmap.light);
j = 0;
}
}
});
}
@Override
public void setMessage(String message) {
str[0]=message;
txt.setText(message);
//updateDataList();
}
@Override
public void setMessage1(String message) {
str[1]=message;
//updateDataList();
}
@Override
public void setMessage2(String message) {
str[2]=message;
//updateDataList();
}
@Override
protected void onDestroy() {
unbindService(serviceConnection);
super.onDestroy();
}
/*
private void initDataList() {
//图片资源
int img[] = {R.mipmap.weathy, R.mipmap.tem, R.mipmap.hum};
String text[] = {"光敏","温度","湿度"};
list = new ArrayList<Map<String, Object>>();
for (int i = 0; i < 3; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("img", img[i]);
map.put("title",text[i]);
map.put("content", "0");
list.add(map);
}
}
private void updateDataList(){
list.clear();
int img[] = {R.mipmap.weathy, R.mipmap.tem, R.mipmap.hum};
String text[] = {"光敏","温度","湿度"};
newlist = new ArrayList<Map<String, Object>>();
for (int i = 0; i < 3; i++) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("img", img[i]);
map.put("title",text[i]);
map.put("content",str[i]);
list.add(map);
}
//txt.setText(message);
list.addAll(newlist);
adapter.notifyDataSetChanged();
}
*/
}
此处的显示图片未截图,后面再补
二、Ble的通讯在安卓中用于和设备连接,是物联网中的近距离通讯连接。
Ble通讯比较特别,需要先搜索,再选择连接,最后通讯。代码太多这里只做搜索ble显示到listview的处理。
2.1 在 AndroidManifest.xml 中添加以下权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.kaka.bluetoothble">
<!--声明蓝牙权限-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/test"
android:label="@string/app_name"
android:roundIcon="@mipmap/test"
android:supportsRtl="true"
android:theme="@style/AppTheme"
>
<activity android:name=".MainActivity"
android:screenOrientation="landscape"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"
android:screenOrientation="landscape"
/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2.2 添加依赖,src文件夹下的build.gradle文件中
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.kaka.bluetoothble"
minSdkVersion 18
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
buildToolsVersion = '28.0.3'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar'
implementation "io.reactivex.rxjava2:rxjava:2.0.6"
implementation "io.reactivex.rxjava2:rxandroid:2.0.1"
implementation "com.blankj:utilcode:1.9.6"
}
2.3 创建 ListView 布局
在 res/layout/activity_main.xml 中创建一个 ListView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/bleListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000" <!-- 设置半透明背景 -->
android:divider="@android:color/transparent"
android:dividerHeight="10dp"/></LinearLayout>
在 res/layout/list_item_ble.xml 中创建每个列表项的布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="#80000000"> <!-- 设置半透明背景 -->
<TextView
android:id="@+id/deviceName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Device Name"
android:textColor="#FFFFFF"
android:textSize="18sp"/>
<TextView
android:id="@+id/deviceAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Device Address"
android:textColor="#FFFFFF"
android:textSize="14sp"/></LinearLayout>
4. 创建 Adapter
在 MainActivity.java 中创建一个自定义的 Adapter 来填充 ListView:
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
public class BleDeviceAdapter extends ArrayAdapter<BluetoothDevice> {
private Context context;
private ArrayList<BluetoothDevice> devices;
public BleDeviceAdapter(Context context, ArrayList<BluetoothDevice> devices) {
super(context, R.layout.list_item_ble, devices);
this.context = context;
this.devices = devices;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.list_item_ble, parent, false);
TextView deviceName = rowView.findViewById(R.id.deviceName);
TextView deviceAddress = rowView.findViewById(R.id.deviceAddress);
BluetoothDevice device = devices.get(position);
deviceName.setText(device.getName());
deviceAddress.setText(device.getAddress());
return rowView;
}
}
5. 在 MainActivity 中使用 Adapter
在 MainActivity.java 中设置 ListView 并使用 BleDeviceAdapter:
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
public class BleDeviceAdapter extends ArrayAdapter<BluetoothDevice> {
private Context context;
private ArrayList<BluetoothDevice> devices;
public BleDeviceAdapter(Context context, ArrayList<BluetoothDevice> devices) {
super(context, R.layout.list_item_ble, devices);
this.context = context;
this.devices = devices;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.list_item_ble, parent, false);
TextView deviceName = rowView.findViewById(R.id.deviceName);
TextView deviceAddress = rowView.findViewById(R.id.deviceAddress);
BluetoothDevice device = devices.get(position);
deviceName.setText(device.getName());
deviceAddress.setText(device.getAddress());
return rowView;
}
}