【Android】基于udp通信的智能家居移动应用开发

发布于:2025-03-20 ⋅ 阅读:(32) ⋅ 点赞:(0)


每文一诗  💪🏼

        男儿不展风云志,空负天生八尺躯 ——《警世通言·卷四十

        译文:作为一个有学识有理想的男子,如果不施展自己的志向,成就一番事业,就枉费生为一个人。


背景

        本次移动应用的开发主要目的是为智能家居开发一个运行在手机的客户端,同时也是为了学习如何开发一个移动应用,这也是我很早之前就想学习的,因为手机现在已经成为每个人都会随身携带的一样东西,通过开发移动应用,可以跟方便快捷的控制我们的下位机,

其中下位机是一个esp_01s的智能门禁和灯控。

具体可参考【嵌入式】ESP_01S智能家居:可二次开发式智能灯控/门禁,勾勒智能生活新图景-CSDN博客

本篇文章使用Android studio IDE,主要介绍的内容是

  • Android端实现udp通信
  • Android如何获取wifi数据
  • activity跳转和不同activity数据传递
  • 弹窗对话框页面实现
  • 退出后数据存储问题
  • 软件打包APK
  • Android studio查看程序输出
  • Android studio终端打印日志

展示

udp通信(发送数据)实现

导入

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
private void udp_sender(String ipaddress,String msg) {
        new Thread(() -> {
            try {
                final int port = 8899;
                InetAddress address = InetAddress.getByName(ipaddress);
                DatagramSocket socket = new DatagramSocket();
                byte[] butter = msg.getBytes();
                DatagramPacket packet = new DatagramPacket(butter, butter.length, address, port);
                socket.send(packet);
                socket.close();
                if(msg.equals("open"))
                    Toastuntil.show_msg(home.this,"成功发送开门信号");
                else if(msg.equals("close"))
                    Toastuntil.show_msg(home.this,"成功发送关门信号");
                else if(msg.equals("open_light"))
                    Toastuntil.show_msg(home.this,"成功发送开灯信号");
                else if(msg.equals("close_light"))
                    Toastuntil.show_msg(home.this,"成功发送关灯信号");
                else if(msg.equals("open_curatin"))
                    Toastuntil.show_msg(home.this,"成功发送拉开窗帘信号");
                else if(msg.equals("close_curatin"))
                    Toastuntil.show_msg(home.this,"成功发送关闭窗帘信号");
                else if (msg.length()==0) {
                    Toastuntil.show_msg(home.this,"输入不能为空");
                    return;
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }


        }).start();
}

🧐函数解析:

该函数在一个新的线程中发送udp消息。

  • InetAddress address = InetAddress.getByName(ipaddress)

这指的是通过函数参数ipaddress,该参数为string型,调用InterAddress类的getbyname方法得到一个inteAddress对象

  • DatagramSocket socket = new DatagramSocket()

这里运用DatagramSocket 类创建一个 UDP 套接字,它能够用于发送和接收 UDP 数据。

  • byte[] butter = msg.getBytes()

这里将函数的参数2,msg是要发送的string型数据,调用其getbytes()方法得到其字节流,因为udp传输的是字节流。

  • DatagramPacket packet = new DatagramPacket(butter, butter.length, address, port);
    

这里运用DatagramPacket类创建了一个对象,该类的构造函数中含有要传输的数据的字节形式,传输数据的长度,目标服务器的IP地址和端口。

  • socket.send(packet);

调用udp套接字的send方法发送DatagramPacket类型的数据。

  • socket.close();

关闭套接字

WiFi模块获取

androidManifest.xml中添加

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

导入

import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;

代码

  private void get_wifi(View v) {
        WifiManager wifimanager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        if(wifimanager!=null && wifimanager.isWifiEnabled())
        {
            WifiInfo wifiInfo = wifimanager.getConnectionInfo();
            if(wifiInfo!=null)
            {
                String ssid = wifiInfo.getSSID();
                String bssid = wifiInfo.getBSSID();
                int rssi = wifiInfo.getRssi();
                mac.setText(bssid);
                sgs.setText(String.valueOf(rssi));
                if(ssid.equals("\"杂物房\""))
                {
                    String text = "lumber room";
                    wifiinfo.setText(text);
                }
                else
                    wifiinfo.setText(ssid);
            }
        }
    }

😈函数解析:

  • WifiManager wifimanager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);

getSystemService方法获取wifimanager实例,wifimanager可用于管理wifi连接和信息

  • wifimanager!=null && wifimanager.isWifiEnabled()

判断wifimanager实例是否为空,并且Wifi功能是否启用

  • WifiInfo wifiInfo = wifimanager.getConnectionInfo();

调用wifimanager的getConnectionInfo()获取Wifi的ssid,bssid,信号强度

  • String ssid = wifiInfo.getSSID();
    String bssid = wifiInfo.getBSSID();
    int rssi = wifiInfo.getRssi();

分别获取Wifi的ssid,bssid,信号强度

调用函数

private View v;
//获取wifi
get_wifi(v);

不同activity数据传递

        🍎在实际的开发中,往往需要不同的页面进行交互,在java中就是不同activity之间的数据传输。

这里就必须要用到一个非常重要的组件了,就是Intent对象。

        用于在不同的组件(如activity,service,broadcastReceiver)之间传递信息,协调不同组件之间的交互

例如

我在这个页面中需要将这个页面的三个IP地址在点击reset后传到上一个页面中

若该类的名称为change,则需使用startActivityForResult方法启动该类,

startActivityForResult是Activity类的一个方法,它允许你启动另一个 Activity,并在该 Activity关闭时接收其返回的数据。

🔥REQUEST_CODE:这是一个整型值,用于标识这次启动 Activity 的请求。当目标 Activity关闭并返回结果时,REQUEST_CODE会被传递回调用者的onActivityResult方法中,这样调用者就可以根据不同的 REQUEST_CODE来处理不同的请求结果。REQUEST_CODE通常是一个自定义的常量,在调用者的类中定义。例如:

  private static final int REQUEST_CODE = 1;
  setipbtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = null;
                intent = new Intent(home.this,change.class);
                //页面跳转
                startActivityForResult(intent, REQUEST_CODE);
            }
        });

则在该activity所属的java类中的oncreate()方法中添加以下代码

activity的oncreate方法会在activity实例化后、第一次显示给用户之前被调用,也是在对象 “创建” 的早期阶段执行。

private Button reset;
private EditText light_et,door_et,curatin_et;
light_et = findViewById(R.id.light_ip);
door_et = findViewById(R.id.door_ip);
curatin_et = findViewById(R.id.curatin_ip);
reset = findViewById(R.id.reset);        
//将该页面的数据传递给下一个activaty
reset.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
   Intent intent = new Intent();
   intent.putExtra("door_ip",door_et.getText().toString());
   intent.putExtra("light_ip",light_et.getText().toString());
   intent.putExtra("curatin_ip",curatin_et.getText().toString());
   setResult(RESULT_OK,intent);
   finish();
   }
});

😇函数解析:

前面主要是获取一些控件对象,就不说了

  • Intent intent = new Intent();

创建一个新的Intent对象,用于携带数据

  • intent.putExtra("door_ip",door_et.getText().toString());
    intent.putExtra("light_ip",light_et.getText().toString());
    intent.putExtra("curatin_ip",curatin_et.getText().toString());

调用Intent的PutExtra方法将door_lp,light_ip,curatin_ip作为键,其内容作为值。

  • setResult(RESULT_OK,intent);

调用 setResult方法设置返回结果,RESULT_OK表示操作成功,同时将携带数据的 intent 作为结果传递回去。

  • finish()

finish()函数结束当前的Activity,返回上一个Actvity。

⚡接收从上一个页面传递的数据

onActivityResult是 Android 中 Activity类的一个回调方法。当你使用 startActivityForResult方法启动另一个 Activity时,被启动的 Activity结束并返回结果时,系统就会调用这个方法

    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == REQUEST_CODE &&resultCode == RESULT_OK)
        {
            if(data!=null)
            {
                _lightip = data.getStringExtra("light_ip");
                _doorip = data.getStringExtra("door_ip");
                _cutationip = data.getStringExtra("curatin_ip");
               
                Toastuntil.show_msg(home.this,"传递成功");
            }

        }
        Toastuntil.show_msg(home.this,"进入");
    }

🔱函数解析:

  • requestCode == REQUEST_CODE &&resultCode == RESULT_OK

requestCode :是一个整型参数,用于标识你启动另一个 Activity 时传入的请求码,startActivityForResult调用时传递的参数。

resultCode:整型参数,代表被启动的Activity返回的结果码。通常有RESULT_OK(表示操作成功)、RESULT_CANCEL(表示操作被取消)

  • data.getStringExtra("light_ip");

通过getstringextra方法来根据键名来访问其内容。

跳转页面/弹窗页面

跳转页面

无数据传递的页面跳转startActivity()

Intent intent = null;
intent = new Intent(home.this,function_activity.class);
startActivity(intent);

有数据传递的页面跳转startActivityForResult()

Intent intent = null;
intent = new Intent(home.this,change_ip.class);
//页面跳转
startActivityForResult(intent, REQUEST_CODE);

弹窗页面(对话框)

这种弹窗页面的实现需要自己新建一个自定义java类,这里以customdialog为例

package com.example.myapplication;

import android.app.Dialog;
import android.os.Bundle;

import android.content.Context;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.graphics.Point;
import android.view.Display;
import android.view.WindowManager;

public class CustomDialog extends Dialog implements View.OnClickListener{

    private static final String TAG = "customDialog";
    private TextView _mtitle;
    private String sTitle,sOk,sCancel;
    private EditText _Name,_Str,_Rollback_val;
    private CheckBox _Rollback_ch;
    private Button _mOk,_mCancel;
    private View.OnClickListener oklistener,cancellistener;
    //!!!注意该类当中的oncreate方法是在这个继承dialog的customdialog类的show方法调用后才调用的
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_button);
        //设置dialog的宽度
        WindowManager m = getWindow().getWindowManager();
        Display d = m.getDefaultDisplay();
        WindowManager.LayoutParams p = getWindow().getAttributes();
        Point size = new Point();
        d.getSize(size);
        p.width = (int) ((size.x)*0.85);        //设置为屏幕的0.85倍宽度
        getWindow().setAttributes(p);


        _mtitle = findViewById(R.id.title);
        _Name =findViewById(R.id.button_name);
        _Str =  findViewById(R.id.send_str);
        _Rollback_ch = findViewById(R.id.rollback_ch);
        _Rollback_val = findViewById(R.id.rollback_val);
        _mOk = findViewById(R.id.ok);
        _mCancel = findViewById(R.id.canel);

        if(this.sTitle!=null)
            _mtitle.setText(this.sTitle);
        if(this.sOk!=null)
            _mOk.setText(this.sOk);
        if(this.sCancel!=null)
            _mCancel.setText(this.sCancel);
        //设置当前对象为事件监听器
        _mOk.setOnClickListener(this);
        _mCancel.setOnClickListener(this);
        Log.d(TAG, "customdialog was ininted");
    }
    //点击事件
    @Override
    public void onClick(View v) {
        if(v.getId()==R.id.ok){
            Log.i(TAG,"ok");
            if(oklistener!=null)
                oklistener.onClick(v);

        }
        else if(v.getId()==R.id.canel){
            Log.i(TAG,"cancel");
                if(cancellistener!=null)
                    cancellistener.onClick(v);
            }
    }
    public CustomDialog(Context context) {
        super(context);
    }
    public  CustomDialog SetButtonname(String text){
        this._Name.setText(text);
        return  this;
    }
    public  CustomDialog SetSendstr(String text){
        this._Str.setText(text);
        return  this;
    }
    public  CustomDialog SetIfrollback(boolean flag){
        this._Rollback_ch.setChecked(flag);
        return  this;
    }
    public  CustomDialog SetRobackval(String text){
        this._Rollback_val.setText(text);
        return  this;
    }
    public CustomDialog SetTitle(String title){
        this.sTitle = title;
        return this;
    }
    public String GetButtonname(){
        String nameval = _Name.getText().toString();
        return nameval;
    }
    public String GetSendstr(){
        String strval = _Str.getText().toString();
        return strval;
    }
    public boolean IfRollback(){
        boolean state = _Rollback_ch.isChecked();
        return state;
    }
    public String GetRollbackval() {
        String roval = _Rollback_val.getText().toString();
        return roval;
    }
    public CustomDialog setok(String val,View.OnClickListener listener ){
        this.oklistener = listener;
        this.sOk = val;
        return this;
    }
    public CustomDialog setcancel(String val,View.OnClickListener listener ){
        this.cancellistener = listener;
        this.sCancel = val;
        return this;
    }

}

🕵🏼类解析:

主要是实现了一些方法

  • WindowManager m = getWindow().getWindowManager();

getWindow方法会返回当前activity或者dialog的window对象,WindowManager负责管理窗口的显示和布局

  • Display d = m.getDefaultDisplay();

Display代表屏幕的显示信息

  • WindowManager.LayoutParams p = getWindow().getAttributes();

获取当前窗口的布局参数LayoutParams,这些参数可以用来设置窗口的大小、位置、透明度等属性。

  • Point size = new Point();

创建一个Point 对象size,用于存储屏幕的尺寸信息

  • d.getSize(size);

将屏幕的宽度和高度信息存储到size对象中

  • p.width = (int) ((size.x)*0.85);        

设置为屏幕的0.85倍宽度

  • getWindow().setAttributes(p);

将修改后的布局参数应用到当前窗口,从而实现窗口宽度的调整

然后编写一个ui

在需要的类当中使用这个对话框类

导入

import com.example.myapplication.CustomDialog;

绑定按键的长按事件,按钮长按时弹出

add1.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                CustomDialog customdialog = new CustomDialog(function_activity.this);

                customdialog.SetTitle("Changed").setok("ok", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //Log.d(TAG,"in");
                        btnname_1 = customdialog.GetButtonname();
                        sendstr_1 = customdialog.GetSendstr();
                        state1 = customdialog.IfRollback();
                        rollbackval1 = customdialog.GetRollbackval();
                        add1.setText(btnname_1);
                        customdialog.dismiss();
                    }
                }).setcancel("cancel", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //关闭对话框
                        Log.d(TAG,"关闭1");
                        customdialog.dismiss();
                    }
                }).show();
                customdialog.SetButtonname(btnname_1);
                customdialog.SetSendstr(sendstr_1);
                customdialog.SetIfrollback(state1);
                customdialog.SetRobackval(rollbackval1);
                return false;
            }
        });

🌱CustomDialog customdialog = new CustomDialog(function_activity.this);
实例化对话框类

并且调用其setcancel方法和setok方法来绑定点击函数,最后通过show()方法来显示对话框

退出后数据保存

保存数据

重写onstop方法

把一些数据保存到sharepreferences里,这样在activity下次启动时就能恢复这些数据,采用键值对的形式存储数据。

protected void onStop() {
        super.onStop();
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString("btnname1",add1.getText().toString());
        editor.putString("btnname2",add2.getText().toString());
        editor.putString("sendstr1",sendstr_1);
        editor.putString("sendstr2",sendstr_2);
        editor.putBoolean("rollbackstate1",state1);
        editor.putString("rollbackval1",rollbackval1);
        editor.putBoolean("rollbackstate2",state2);
        editor.putString("rollbackval2",rollbackval2);
        editor.putString("ip",ed1.getText().toString());
        editor.commit();
    }

🧐函数分析:

  • SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

PreferenceManager.getDefaultSharedPreferences(this)方法获取默认的 SharedPreferences实例,它是 Android 里用于存储轻量级数据的一种机制,采用键值对的形式存储数据

  • SharedPreferences.Editor editor = sharedPreferences.edit();

通过edit()方法获取SharedPreferences的编辑器,借助这个编辑器可以对 SharedPreferences中的数据进行修改。

  • editor.putString("btnname1",add1.getText().toString());

使用其Putxxx()方法提交指定数据到SharedPreferences的编辑器。

  • editor.commit();

调用commit()方法将编辑器中的修改提交到SharedPreferences中,这样数据就会被持久化存储。

恢复保存数据

在需要恢复的页面中的java类的oncreate()方法中

SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

并使用其getxxx()方法来根据键名称来获取其内容

sharedPreferences.getString("btnname1","+")

软件打包APK

打开android studio

选择apk

输入密码next

选择relesase

Android studio终端打印日志

在 Android 开发中,TAG主要用于日志输出。借助Log类输出日志时,需要提供一个标签,以此对日志进行分类和筛选

例如

import android.util.Log;
private static final String TAG = "home_activity";
  • TAG:这是变量名,按照 Android 开发的惯例,一般会用TAG来表示日志标签。
  • home_activity:这是TAG的具体值,一般会使用当前类的名称或者与类功能相关的名称,这样在调试时能清晰知道日志是从哪个类输出的。

导入

import android.util.Log;

使用Log的d,i,w,e分别输出调试、信息、警告和错误级别的日志。

Log.d(TAG, "debug message");
Log.i(TAG, "info message");
Log.w(TAG, "warning message");
Log.e(TAG, "error message");

Android studio查看程序输出

在刚刚接触android studio时候,不知道从哪里看程序的输出,后来才知道是这样看的。

打开android studio后找到做侧边栏的Logcat

然后再这个框框里面输入你想要查看那个java类的输出,直接输入该类名字即可。