小白速成法:剖析一个Android项目以快速上手

这是一个基于Tasmota的设备、用MQTT协议来通信控制的安卓应用程序。支持ON/OFF命令插座和基本的RGB LED控制。

源码点击此处

只需要关注SmartController-main\app\src的代码

项目解压之后如图

只需要关注“app”文件夹里的东西即可,“gradle”是配置文件,和Android studio的安装环境有关,后续打算出一个讲这部分的

只需要关注“src”文件夹里的东西即可,“build”是 Android 应用构建过程中生成的临时文件和输出,主要涉及编译过程中生成的中间文件以及用于加速编译的临时数据,不需要修改

手动更改这些文件可能会导致构建错误或不稳定的行为。通常,Android Studio和构建工具会负责处理这些生成的文件,你只需专注于修改 D:\AndroidCode\SmartController-main\app\src 目录下的源代码和资源文件,以及适当地修改 build.gradle 等配置文件。

Android studio项目中看到的文件夹

都来自于src文件,src 是 "source"(源代码)的缩写,用于存放应用程序的源代码

为什么在 Android Studio 中打开文件夹和实际文件系统中的文件夹看起来不一致:

        1、过滤或忽略文件: Android Studio 可能会根据项目设置或 IDE 配置文件中的规则来过滤或忽略某些文件或文件类型,例如,临时文件、构建输出等。

        2、链接文件或文件夹: 在文件系统中,可能存在符号链接或快捷方式指向其他文件或文件夹,而 Android Studio 可能会展示这些链接文件或文件夹的实际内容。

以下几个图示可以知道Android Studio里的项目的实际文件路径

AndroidManifest.xml

com.leondeklerk.smartcontroller

java代码+少数kotlin代码

xml代码

接下来就是针对上述4个模块的代码进行详细解释

   

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 定义 Android 清单文件 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><!-- 请求访问网络状态的权限 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><!-- 请求访问互联网的权限 --><uses-permission android:name="android.permission.INTERNET" /><!-- 定义应用程序的配置信息 --><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/MyTheme.DayNight"android:usesCleartextTraffic="true"><!-- 定义设置界面的活动 --><activityandroid:name=".SettingsActivity"android:label="@string/title_activity_settings" /><!-- 定义主界面的活动 --><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><!-- 设置为主活动,应用启动时打开该活动 --><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><!-- 定义设备编辑界面的活动 --><activityandroid:name=".DeviceEditActivity"android:parentActivityName=".MainActivity"android:windowSoftInputMode="adjustPan" /><!-- 定义设备颜色选择界面的活动 --><activityandroid:name=".DeviceColorActivity"android:parentActivityName=".MainActivity" /></application>
</manifest>

com.leondeklerk.smartcontroller

"com.leondeklerk.smartcontroller" 是一个应用程序的包名(Package Name),根据通常的Android应用命名规范。应用程序的包名是在开发应用时定义的唯一标识符,通常采用反转的域名形式(例如 com.example.myapp)。

其在实际文件夹中就是一个连续子文件夹

主要项目代码都在“main”里,另外两个只是双端简单的测试代码

“androidTest”是一个基本的Instrumented测试类,用于在Android设备上执行测试

/*** Instrumented test, which will execute on an Android device.* 仪器化测试,将在 Android 设备上执行。* * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>* 参见测试文档链接*/
@RunWith(AndroidJUnit4.class)
// 使用 AndroidJUnit4 运行器来执行测试
public class ExampleInstrumentedTest {@Test// 注解标识该方法是一个测试方法public void useAppContext() {// Context of the app under test.// 获取被测试应用的上下文对象Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();// 断言被测试应用的包名是否符合预期值assertEquals("com.leondeklerk.smartcontroller", appContext.getPackageName());}
}

“test”是一个基本的JUnit Jupiter(JUnit 5)本地单元测试类,用于在开发机器(主机)上执行测试

/*** Example local unit test, which will execute on the development machine (host).* 示例本地单元测试,将在开发机器(主机)上执行。* * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>* 参见测试文档链接*/
public class ExampleUnitTest {@Test// 注解标识该方法是一个测试方法public void addition_isCorrect() {// 使用断言验证加法是否正确Assertions.assertEquals(4, 2 + 2);}
}

“main”里的java代码+少数kotlin代码

data 目录:

        Command.java - 包含与设备通信的指令相关的类。

        DeviceData.java - 包含设备数据的模型类。

        Entry.java - 用于表示数据项的类。

devices 目录:

        RGBLedController.java - 一个 RGB LED 控制器设备的实现类。

        SmartDevice.java - 通用智能设备的实现类。

utils 目录:

        DeviceStorageUtils.java - 包含用于设备数据存储的工具方法。

        DiffUtilCallback.java - 是用于处理列表数据变更的回调类。

        TextInputUtils.java - 包含处理文本输入的实用方法。

widget 目录:

        ColorDotView.kt - 是一个用 Kotlin 编写的自定义颜色点视图,可能用于界面显示。(就这里用了kotlin)

其它:

        ConnectionsHandler.java - 处理与设备的连接和通信的类。

        DeviceAdapter.java - 设备列表的适配器类,用于在界面上显示设备列表。

        DeviceColorActivity.java - 设备颜色控制的活动类。

        DeviceEditActivity.java - 设备编辑界面的活动类。

        DeviceEditFragment.java - 设备编辑界面的片段类。

        MainActivity.java - 应用的主活动类。

        MqttClient.java - MQTT(Message Queuing Telemetry Transport)客户端类,用于消息传递。

        NetworkHandler.java - 处理网络连接的类。

        SettingsActivity.java - 应用设置界面的活动类。

data 目录

Command.java
/*** A class that represents a new MQTT command. Contains a topic and message.* 表示一个新的MQTT命令的类。包含主题和消息。*/
public class Command {private String topic;   // 存储消息将要发布的主题private String message; // 存储将要发布的消息内容/*** Class constructor.* 类的构造方法。** @param topic   the topic that the message will be published on.*                消息将要发布的主题。* @param message the message that will be published.*                将要发布的消息。*/public Command(String topic, String message) {this.topic = topic;this.message = message;}// 获取消息将要发布的主题public String getTopic() {return topic;}// 设置消息将要发布的主题public void setTopic(String topic) {this.topic = topic;}// 获取将要发布的消息内容public String getMessage() {return message;}// 设置将要发布的消息内容public void setMessage(String message) {this.message = message;}
}
DeviceData.java
/*** 表示与设备相关的所有数据的类。每个设备包含一组数据,这些数据在所有SmartDevices之间共享。此类扩展了BaseObservable以适应与UI的数据绑定。*/
public class DeviceData extends BaseObservable {private final int id;        // 设备的唯一标识符private String name;         // 设备的名称private String status;       // 设备的状态private boolean enabled;     // 设备是否启用private final String type;   // 设备的类型private String topic;        // 设备将要监听的主题/*** Default constructor* 默认构造方法** @param id     the id of this device*               此设备的唯一标识符* @param name   the name of the device*               设备的名称* @param status the status of the device*               设备的状态* @param enabled indicates if the device is enabled or not*                表示设备是否启用* @param type   the type of the device*               设备的类型* @param topic  the topic this device will listen to*               此设备将要监听的主题*/public DeviceData(int id, String name, String status, boolean enabled, String type, String topic) {this.id = id;this.name = name;this.status = status;this.enabled = enabled;this.type = type;this.topic = topic;}@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()) {return false;}DeviceData that = (DeviceData) o;return id == that.id&& enabled == that.enabled&& Objects.equals(topic, that.topic)&& Objects.equals(name, that.name)&& Objects.equals(status, that.status);}public int getId() {return id;}public String getName() {return name;}/*** Set the name of the device* 设置设备的名称** @param name the new name of the device*             设备的新名称* @return this*/public DeviceData setName(String name) {this.name = name;notifyPropertyChanged(BR._all);return this;}public String getTopic() {return topic;}/*** Set the topic of this device.* 设置此设备的主题。** @param topic the new topic.*              新的主题* @return this instance.*/public DeviceData setTopic(String topic) {this.topic = topic;notifyPropertyChanged(BR._all);return this;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public boolean isEnabled() {return enabled;}public void setEnabled(boolean enabled) {this.enabled = enabled;}public String getType() {return type;}
}
Entry.java
/*** 表示设备存储数据结构中条目的类。包含一个 id 和设备的引用。id 表示设备在列表中的位置,而设备是实际存储的设备。*/
public class Entry {private final int id;            // 设备在列表(例如MainActivity RecyclerView)中的位置private final SmartDevice device; // 实际存储的设备引用/*** Default constructor* 默认构造方法** @param id     the id of the device in the list (MainActivity RecyclerView)*               设备在列表中(例如MainActivity RecyclerView)的位置* @param device the device itself.*               设备本身*/public Entry(int id, SmartDevice device) {this.id = id;this.device = device;}// 获取设备在列表中的位置public int getId() {return id;}// 获取设备引用public SmartDevice getDevice() {return device;}
}

devices 目录

RGBLedController.java
// 表示这是一个LED控制器的SmartDevice的特定实例
public class RGBLedController extends SmartDevice {/*** 默认构造方法** @param data the data that represents this device.*             表示此设备的数据。*/public RGBLedController(DeviceData data) {super(data);}/*** 获取用于检查状态的命令。** @return the color status Command*/public Command getColor() {return new Command(super.getTopic("Color"), "?");}/*** 设置设备的颜色。** @param red the value of red.*            红色通道的值。* @param green the value of green.*              绿色通道的值。* @param blue the value of blue.*             蓝色通道的值。* @return a new command that will be published on the MQTT client.*         将在MQTT客户端上发布的新命令。*/@SuppressLint("DefaultLocale")public Command setColor(int red, int green, int blue) {return new Command(super.getTopic("Color2"), String.format("%d,%d,%d", red, green, blue));}
}
SmartDevice.java
/*** SmartDevice是所有支持的设备的基类。这包括封装基本数据,如id、名称、IP和可选凭证。该类还提供了一些基本命令,如检查电源状态、打开或关闭电源。其他设备可以从这个类扩展,以提供额外的功能,如LED的颜色控制。*/
public class SmartDevice {private final DeviceData data; // 设备的基本数据/*** Default constructor to create a new SmartDevice, based on some given device data.* 根据给定的设备数据创建一个新的SmartDevice的默认构造方法。** @param data the data for this device.*             此设备的数据。*/public SmartDevice(DeviceData data) {this.data = data;}@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()) {return false;}return this.getData().equals(((SmartDevice) o).getData());}/*** 根据设备的数据和命令的类型获取要发布的主题。** @param command the command that the device needs to execute.*                设备需要执行的命令。* @return the topic formatted with the device data topic.*         使用设备数据主题格式化的主题。*/String getTopic(String command) {return "cmnd/" + data.getTopic() + "/" + command;}/*** 获取用于检查状态的命令。** @return the power status Command*/public Command getPowerStatus() {return new Command(getTopic("POWER"), "?");}/*** 获取打开或关闭电源的命令。** @param on boolean indicating whether or not to turn the power the device on or off.*           布尔值,表示是否打开或关闭设备电源。* @return the command to turn the device on or off.*         打开或关闭设备的命令。*/public Command setPower(boolean on) {return new Command(getTopic("POWER"), on ? "ON" : "OFF");}/*** 克隆一个设备并返回具有另一个内存地址的精确副本。** @param other the device to clone.*              要克隆的设备。* @return a cloned instance of the other device.*         另一个设备的克隆实例。*/public static SmartDevice clone(SmartDevice other) {DeviceData otherData = other.getData();return new SmartDevice(new DeviceData(otherData.getId(),otherData.getName(),otherData.getStatus(),otherData.isEnabled(),otherData.getType(),otherData.getTopic()));}public DeviceData getData() {return data;}
}

utils 目录

DeviceStorageUtils.java
// 一个用于处理在应用程序的SharedPreferences中存储和检索设备的类
public class DeviceStorageUtils {private SharedPreferences preferences; // SharedPreferences对象private Context context;               // 上下文对象/*** Basic constructor for the DeviceStorageUtils class.* DeviceStorageUtils类的基本构造方法。** @param preferences the preferences to store and retrieve in/from.*                    用于存储和检索的SharedPreferences对象* @param context the context of the application.*                应用程序的上下文对象*/public DeviceStorageUtils(SharedPreferences preferences, Context context) {this.preferences = preferences;this.context = context;}/*** 从SharedPreferences中的String使用GSON库检索所有SmartDevices的方法。** @return a list of retrieved SmartDevices.*         检索到的SmartDevices列表。*/public ArrayList<SmartDevice> getDevices() {String json = preferences.getString("deviceList", null);if (json != null) {Gson gson = new Gson();// Convert back to a Java ObjectType type = new TypeToken<ArrayList<SmartDevice>>() {}.getType();return gson.fromJson(json, type);} else {return new ArrayList<>();}}/*** A method that converts a list of SmartDevices to a String and stores it in the* SharedPreferences specified by the class Object.* 将SmartDevices列表转换为String并将其存储在由类对象指定的SharedPreferences中的方法。** @param devices the list of devices to store.*                要存储的设备列表。*/public void storeDevices(ArrayList<SmartDevice> devices) {// Set the status to unknown (prevent the status from being stored)// 将状态设置为未知(防止状态被存储)for(SmartDevice device : devices) {device.getData().setStatus(context.getString(R.string.status_unknown));}Editor prefsEditor = preferences.edit();Gson gson = new Gson();// Convert the object to a StringString json = gson.toJson(devices);// Store the stringprefsEditor.putString("deviceList", json);prefsEditor.apply();}
}
DiffUtilCallback.java
/*** 一个处理两个ArrayList之间差异计算的类。用于更新RecyclerView及其相应的适配器。*/
public class DiffUtilCallback extends Callback {private ArrayList<SmartDevice> oldList; // 旧列表private ArrayList<SmartDevice> newList; // 新列表/*** Default constructor, taking in the two lists that need to be compared.* 默认构造方法,接收需要进行比较的两个列表。** @param oldList list one.*                列表一* @param newList list two.*                列表二*/public DiffUtilCallback(ArrayList<SmartDevice> oldList, ArrayList<SmartDevice> newList) {this.oldList = oldList;this.newList = newList;}@Overridepublic int getOldListSize() {return oldList.size();}@Overridepublic int getNewListSize() {return newList.size();}@Overridepublic boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));}@Overridepublic boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {return oldList.get(oldItemPosition).getData().equals(newList.get(newItemPosition).getData());}
}
TextInputUtils.java
/*** 一组用于与TextInputLayouts交互的实用方法的集合。功能从设置监听器到检查错误和比较值等。*/
public class TextInputUtils {public static final String DEV_TYPE_DEF = "DEFAULT_TYPE";public static final String DEV_TYPE_RGB = "RGB_CONTROLLER_TYPE";// An input type that is a field with a max lengthpublic static final String DEFAULT_TYPE = "DEFAULT_TYPE";/*** 检查布局列表中是否有任何错误,还检查是否为空的布局。** @param layouts the ArrayList of TextInputLayouts to check.* @return true if there are errors, false if not.*/public static boolean hasErrors(ArrayList<TextInputLayout> layouts) {// Check if one of the layouts is emptyisEmpty(layouts);for (TextInputLayout layout : layouts) {if (layout.getError() != null) {// If a layout has an error, return true and request the focus on that one.layout.requestFocus();return true;}}return false;}/*** 检查TextInputLayouts的ArrayList是否有任何空字段。如果有一个字段为空,将设置正确的错误。** @param layouts the list of layouts.*/@SuppressWarnings("ConstantConditions")private static void isEmpty(ArrayList<TextInputLayout> layouts) {for (TextInputLayout layout : layouts) {// Get the text and resources from the layoutString text = layout.getEditText().getText().toString();Resources resources = layout.getResources();if (TextUtils.isEmpty(text)) {// If empty, set an errorlayout.setError(resources.getString(R.string.error_input_required));}}}/*** 通过读取一个TextInputLayouts的ArrayList和一些附加信息,创建一个新的SmartDevice。基于这些值,将创建并返回一个新的SmartDevice。** @param layouts the list of fields to read the data from.* @param nextId the id of this new device.* @return a new SmartDevice based on the read data.*/@SuppressWarnings("ConstantConditions")public static SmartDevice readDevice(Context context, String type, ArrayList<TextInputLayout> layouts, int nextId) {ArrayList<String> inputs = new ArrayList<>();// Read each input and add it to the list of inputsfor (TextInputLayout layout : layouts) {EditText editText = layout.getEditText();inputs.add(editText.getText().toString());}// Create a new deviceDeviceData data =new DeviceData(nextId,inputs.get(0),context.getString(R.string.status_unknown),false,type,inputs.get(1));// Return the type of deviceif (type.equals(DEV_TYPE_RGB)) {return new RGBLedController(data);}return new SmartDevice(data);}/*** 从TextInputLayout中检索字符串。** @param layout the layout to retrieve the text from.* @return the input text.*/@SuppressWarnings("ConstantConditions")public static String getText(TextInputLayout layout) {return layout.getEditText().getText().toString();}/*** 设置正确的过滤器和错误侦听器以处理用户输入中的错误。** @param layout the layout to set the filter on.* @param type the type of input field, only option now is DEFAULT_TYPE.*/@SuppressWarnings("ConstantConditions")public static void setListener(final TextInputLayout layout, String type) {if (DEFAULT_TYPE.equals(type)) { // The default type needs an error handler for surpassing the maximum length.layout.getEditText().addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {}@Overridepublic void afterTextChanged(Editable s) {// If the length is too great, write an errorif (s.length() > layout.getCounterMaxLength()) {Resources resources = layout.getResources();layout.setError(resources.getString(R.string.error_input_length));} else {layout.setError(null);}}});} else {Log.d("TextInputLayout type", type);}}
}

widget 目录

ColorDotView.kt
/** 版权所有 2019 年 Android 开源项目** 根据 Apache 许可证 2.0 版本(以下简称“许可证”)获得许可;* 您不得使用此文件,除非符合许可证的规定。* 您可以在以下网址获得许可证副本:**     http://www.apache.org/licenses/LICENSE-2.0** 除非适用法律要求或书面同意,否则按“原样”分发软件,* 无任何形式的明示或暗示的保证或条件。* 有关许可证下的特定语言,请参阅许可证。*//*** 该项目代码简单绘制了带有描边的填充圆圈。*/
class ColorDotView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {var fillColor: Int = Color.LTGRAYset(value) {paintFill.color = valuefield = valuethis.invalidate()}private val paintFill = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.FILLcolor = Color.RED}private var cx: Float = 0Fprivate var cy: Float = 0Fprivate var radius: Float = 0Finit {// 从 XML 属性获取填充颜色val a = context.theme.obtainStyledAttributes(attrs,R.styleable.ColorDotView,defStyleAttr,0)fillColor = a.getColor(R.styleable.ColorDotView_colorFillColor, fillColor)a.recycle()}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)// 计算圆的位置和半径cx = w / 2Fcy = h / 2F// 稍微减小我们圆的半径,以防止描边被裁剪。radius = (w / 2F) - 1F}override fun onDraw(canvas: Canvas) {// 在 Canvas 上绘制圆圈canvas.drawCircle(cx, cy, radius, paintFill)}
}

其它

ConnectionsHandler.java
//一个接口,用于处理所有需要得到适当处理的不同类型的连接。包含用于MQTT回调和NetworkHandler类的回调的方法。
public interface ConnectionsHandler {/*** 当"stat/+/RESULT"主题上的新MqttMessage到达时的回调。从这里开始,可以解析并相应地处理消息。** @param topic 收到消息的主题。* @param message 收到的实际消息。*/void onMqttMessage(String topic, MqttMessage message);void onMqttSubscribe();//当MqttClient成功订阅主题时的回调。用于通知活动客户端现在已完全设置并准备好。/*** 当客户端连接到服务器时使用的回调。尚未建立订阅。用于处理连接后需要设置的订阅和其他参数。* @param connected 指示客户端是否连接的标志。*/void onMqttConnected(boolean connected);void onNetworkChange();//设备网络更改时的回调。用于在例如WiFi重新连接时适当处理状态更新。
}
DeviceAdapter.java
/*** 用于填充RecyclerView的SmartDevice实例的适配器,用于显示SmartDevice实例的卡片*/
public class DeviceAdapter extends RecyclerView.Adapter<CardViewHolder> {private final ArrayList<SmartDevice> devices;private final Activity context;/*** RecyclerView中每个卡片的视图。*/static class CardViewHolder extends RecyclerView.ViewHolder {ComponentCardsBinding binding;/*** 默认构造函数* @param binding 表示视图的绑定。*/CardViewHolder(ComponentCardsBinding binding) {super(binding.getRoot());this.binding = binding;}/*** 将smartDevice绑定到布局。** @param device 要绑定的设备。*/public void bind(SmartDevice device) {binding.setDevice(device);binding.executePendingBindings();}}/*** 适配器的默认构造函数,接受上下文和设备列表。** @param devices 用于创建此适配器的设备。* @param context 用于使用的应用程序上下文。*/DeviceAdapter(ArrayList<SmartDevice> devices, Activity context) {this.devices = devices;this.context = context;}// 创建新视图(由布局管理器调用)@NotNull@Overridepublic CardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {// 为此项创建一个新的MaterialCardViewLayoutInflater inflater = LayoutInflater.from(parent.getContext());ComponentCardsBinding binding = ComponentCardsBinding.inflate(inflater, parent, false);return new CardViewHolder(binding);}// 替换视图的内容(由布局管理器调用)@Overridepublic void onBindViewHolder(@NotNull CardViewHolder holder, final int pos) {final int position = holder.getAdapterPosition();final SmartDevice device = devices.get(position);holder.bind(device);ComponentCardsBinding binding = holder.binding;// 编辑Activity的按钮binding.deviceEdit.setOnClickListener(v -> {Intent intent = new Intent(context, DeviceEditActivity.class);intent.putExtra(DeviceEditActivity.EXTRA_SELECTED_DEV, position);intent.putExtra(DeviceEditActivity.EXTRA_NUM_DEV, getItemCount());context.startActivityForResult(intent, 0);});// 颜色Activity的按钮binding.deviceColor.setOnClickListener(v -> {Intent intent = new Intent(context, DeviceColorActivity.class);intent.putExtra(DeviceColorActivity.EXTRA_SELECTED_DEV, position);context.startActivity(intent);});// 电源的开关binding.devicePower.setOnCheckedChangeListener((buttonView, isChecked) -> {// 检查是否由用户按下(而不是其他什么)if (buttonView.isPressed()) {MqttClient client = ((MainActivity) context).getMqttClient();client.publish(device.setPower(isChecked));}});}// 返回数据集的大小(由布局管理器调用)@Overridepublic int getItemCount() {return devices.size();}
}
DeviceColorActivity.java
/*** 这个DeviceColorActivity用于处理显示和更新RGBLedController的颜色,它包括与MQTT客户端的交互和网络更改的响应。该活动使用相应的布局和UI元素来显示颜色信息,并在用户交互时执行相应的操作。*/
public class DeviceColorActivity extends FragmentActivityimplements View.OnClickListener, ConnectionsHandler {public static final String EXTRA_SELECTED_DEV = "com.leondeklerk.smartcontroller.SELECTED_DEV";private ActivityDeviceColorBinding binding;private RGBLedController device;private MqttClient client;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);NetworkHandler handler = NetworkHandler.getHandler();handler.setCurrentHandler(this);binding = ActivityDeviceColorBinding.inflate(getLayoutInflater());View view = binding.getRoot();setContentView(view);binding.toolbar.setNavigationOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {onBackPressed();}});Intent intent = getIntent();int deviceNum = intent.getIntExtra(EXTRA_SELECTED_DEV, 0);// 设置MqttCient并注册正确的接收器。client = MqttClient.getInstance(getApplicationContext());client.registerHandler("DeviceColorActivity", this);client.setHandler("DeviceColorActivity");SharedPreferences preferences =this.getSharedPreferences(getString(R.string.dev_prefs), Context.MODE_PRIVATE);DeviceStorageUtils deviceStorageUtils = new DeviceStorageUtils(preferences, this);ArrayList<SmartDevice> devices = deviceStorageUtils.getDevices();device = new RGBLedController(devices.get(deviceNum).getData());client.publish(device.getColor());binding.colorInfo.setText(device.getData().getName());binding.colorCancel.setOnClickListener(this);binding.colorSet.setOnClickListener(this);}@Overridepublic void onClick(View v) {int id = v.getId();if (id == R.id.color_cancel) {this.onBackPressed();} else if (id == R.id.color_set) {int red = (int) binding.sliderRed.getValue();int green = (int) binding.sliderGreen.getValue();int blue = (int) binding.sliderBlue.getValue();client.publish(device.setColor(red, green, blue));} else {Log.d("DeviceColorActivity@onClick", "Non-existent button clicked (color)");}}@Overrideprotected void onDestroy() {super.onDestroy();client.setHandler("MainActivity");}@Overridepublic void onMqttMessage(String topic, MqttMessage message) {parseResponse(message);}@Overridepublic void onMqttSubscribe() {}@Overridepublic void onMqttConnected(boolean connected) {}@Overridepublic void onNetworkChange() {client = MqttClient.reconnect(this);}/*** 解析接收到的MQTT消息的响应并相应地更新布局。** @param message 要解析的消息。*/private void parseResponse(MqttMessage message) {String colorString = "";try {JSONObject obj = new JSONObject(message.toString());colorString = obj.getString("Color");} catch (JSONException e) {e.printStackTrace();}String[] colors = colorString.split(",");binding.sliderRed.setValue(Float.parseFloat(colors[0]));binding.sliderGreen.setValue(Float.parseFloat(colors[1]));binding.sliderBlue.setValue(Float.parseFloat(colors[2]));}
}
DeviceEditActivity.java
/** 这个DeviceEditActivity用于编辑设备数据,它包含一个ViewPager2,通过它可以左右滑动切换不同的DeviceEditFragment。DeviceEditFragmentAdapter是ViewPager2的适配器,负责管理所有的DeviceEditFragment。在onCreate方法中,根据传递的Intent设置ViewPager2和适配器,并指定当前显示的页面。 */
public class DeviceEditActivity extends FragmentActivity {public static final String EXTRA_SELECTED_DEV = "com.leondeklerk.smartcontroller.SELECTED_DEV";public static final String EXTRA_NUM_DEV = "com.leondeklerk.smartcontroller.NUM_DEV";private static int numOfDevices;static ActivityDeviceEditBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityDeviceEditBinding.inflate(getLayoutInflater());View view = binding.getRoot();setContentView(view);binding.toolbar.setNavigationOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {onBackPressed();}});Intent intent = getIntent();numOfDevices = intent.getIntExtra(EXTRA_NUM_DEV, 0);ViewPager2 viewPager = binding.pager;FragmentStateAdapter pagerAdapter = new DeviceEditFragmentAdapter(this);viewPager.setAdapter(pagerAdapter);viewPager.setCurrentItem(intent.getIntExtra(EXTRA_SELECTED_DEV, 0));}/** 包含所有DeviceEditFragments的适配器。 */private static class DeviceEditFragmentAdapter extends FragmentStateAdapter {/*** 默认构造函数。** @param fragmentActivity 与此片段相关的Activity。*/DeviceEditFragmentAdapter(FragmentActivity fragmentActivity) {super(fragmentActivity);}@NotNull@Overridepublic Fragment createFragment(int position) {Fragment fragment = new DeviceEditFragment();Bundle args = new Bundle();args.putInt(DeviceEditFragment.ARG_FRAG_NUM, position);fragment.setArguments(args);return fragment;}@Overridepublic int getItemCount() {return numOfDevices;}}
}
DeviceEditFragment.java
/*** 代表设备编辑屏幕中的实际设备编辑界面的Fragment。包含设备的所有数据以及更改此数据的选项。*/
public class DeviceEditFragment extends Fragment implements View.OnClickListener {static final String ARG_FRAG_NUM = "com.leondeklerk.smartcontroller.FRAG_NUM";private Activity context;private int devNum;private ArrayList<SmartDevice> devices;private DeviceStorageUtils deviceStorageUtils;private FragmentDeviceEditBinding binding;private SmartDevice device;private SmartDevice initial;private ArrayList<TextInputLayout> fragList;@Overridepublic View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {binding = FragmentDeviceEditBinding.inflate(inflater, container, false);context = getActivity();return binding.getRoot();}@Overridepublic void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) {Bundle args = getArguments();if (args != null) {devNum = args.getInt(ARG_FRAG_NUM);} else {context.finish();}SharedPreferences preferences =context.getSharedPreferences(getString(R.string.dev_prefs), Context.MODE_PRIVATE);deviceStorageUtils = new DeviceStorageUtils(preferences, context);devices = deviceStorageUtils.getDevices();device = devices.get(devNum);// 设置对当前设备的引用initial = SmartDevice.clone(device);// 绑定数据类binding.setDevice(device);binding.executePendingBindings();// 设置按钮监听器binding.editDelete.setOnClickListener(this);binding.editUpdate.setOnClickListener(this);setUpUtilsFrag();}@Overridepublic void onResume() {super.onResume();// 更改Activity的标题DeviceEditActivity.binding.toolbar.setTitle(device.getData().getName());}@Overridepublic void onDestroyView() {super.onDestroyView();binding = null;}@SuppressLint("NonConstantResourceId")@Overridepublic void onClick(View v) {int id = v.getId();if (id == R.id.edit_delete) {// 删除设备并存储devices.remove(devNum);deviceStorageUtils.storeDevices(devices);setResult(true);// 返回context.onBackPressed();} else if (id == R.id.edit_update) {if (!TextInputUtils.hasErrors(fragList)) {// 更新设备并返回updateDevice();context.onBackPressed();} else {setResult(false);}} else {Log.d("DeviceEditFragment@onClick", String.valueOf(id));}}/** 设置Fragment中的输入字段,添加它们的错误监听器。 */private void setUpUtilsFrag() {fragList = new ArrayList<>();// 将所有输入布局添加到列表中fragList.add(binding.editName);fragList.add(binding.editTopic);// 设置错误监听器TextInputUtils.setListener(binding.editName, TextInputUtils.DEFAULT_TYPE);TextInputUtils.setListener(binding.editTopic, TextInputUtils.DEFAULT_TYPE);}/*** 设置父Activity的结果Intent,将在MainActivity重新进入时进行检查。** @param removed 如果设备已删除,则为true;否则为false*/private void setResult(boolean removed) {// 创建一个新的IntentIntent resultIntent = new Intent();if (removed) {// 如果设备已删除,则标记此项resultIntent.putExtra(MainActivity.EXTRA_DEV_REMOVED, devNum);} else {if (!initial.equals(device)) {// 如果设备已编辑,则标记此项resultIntent.putExtra(MainActivity.EXTRA_DEV_CHANGED, devNum);}}context.setResult(Activity.RESULT_OK, resultIntent);}/** 更新并存储当前设备。 */private void updateDevice() {// 更新设备数据device.getData().setName(TextInputUtils.getText(binding.editName)).setTopic(TextInputUtils.getText(binding.editTopic));setResult(false);// 存储新的设备数据deviceStorageUtils.storeDevices(devices);}
}
MainActivity.java

这个类包含了许多与Android应用相关的功能,包括RecyclerView的使用、对话框的创建、与MQTT服务器的通信、偏好设置的处理等。

import内容
import android.content.Context; // 导入Android上下文相关的类,用于获取应用程序的上下文信息
import android.content.DialogInterface; // 导入Android对话框相关的类,用于创建对话框
import android.content.Intent; // 导入Android意图相关的类,用于启动其他组件或应用
import android.content.SharedPreferences; // 导入Android共享首选项相关的类,用于存储和获取应用程序的配置信息
import android.os.Bundle; // 导入Android包相关的类,用于处理应用程序的状态和数据
import android.util.Log; // 导入Android日志类,用于输出调试和信息日志
import android.util.Pair; // 导入Android Pair类,用于保存一对对象
import android.view.LayoutInflater; // 导入Android布局加载相关的类,用于动态加载布局
import android.view.MenuItem; // 导入Android菜单项相关的类,用于处理菜单项的点击事件
import android.view.View; // 导入Android视图相关的类,用于构建用户界面
import android.widget.Button; // 导入Android按钮相关的类,用于创建按钮
import android.widget.Toast; // 导入Android Toast类,用于显示短暂的提示消息import androidx.appcompat.app.AlertDialog; // 导入Android支持库中的对话框类,用于创建更灵活的对话框
import androidx.appcompat.app.AppCompatActivity; // 导入Android支持库中的AppCompatActivity类,用于创建兼容旧版Android的活动
import androidx.appcompat.widget.Toolbar; // 导入Android支持库中的工具栏类,用于创建应用程序的工具栏
import androidx.recyclerview.widget.DiffUtil; // 导入Android支持库中的DiffUtil类,用于计算列表差异
import androidx.recyclerview.widget.DiffUtil.DiffResult; // 导入DiffUtil的DiffResult类,表示计算差异的结果
import androidx.recyclerview.widget.LinearLayoutManager; // 导入Android支持库中的LinearLayoutManager类,用于设置RecyclerView的布局管理器
import androidx.recyclerview.widget.RecyclerView; // 导入Android支持库中的RecyclerView类,用于显示列表数据
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; // 导入Android支持库中的SwipeRefreshLayout类,用于添加下拉刷新功能import com.google.android.material.dialog.MaterialAlertDialogBuilder; // 导入Material Design库中的AlertDialogBuilder类,用于创建Material风格的对话框
import com.google.android.material.textfield.TextInputLayout; // 导入Material Design库中的TextInputLayout类,用于创建包含文本输入字段的布局
import com.leondeklerk.smartcontroller.data.Entry; // 导入自定义数据类Entry,用于封装设备条目信息
import com.leondeklerk.smartcontroller.databinding.ActivityMainBinding; // 导入自动生成的ActivityMainBinding类,用于绑定MainActivity的布局
import com.leondeklerk.smartcontroller.databinding.DeviceDialogBinding; // 导入自动生成的DeviceDialogBinding类,用于绑定设备对话框的布局
import com.leondeklerk.smartcontroller.devices.SmartDevice; // 导入自定义SmartDevice类,用于表示智能设备的数据和操作
import com.leondeklerk.smartcontroller.utils.DeviceStorageUtils; // 导入自定义DeviceStorageUtils类,用于设备数据的存储和检索
import com.leondeklerk.smartcontroller.utils.DiffUtilCallback; // 导入自定义DiffUtilCallback类,用于计算两个列表之间的差异
import com.leondeklerk.smartcontroller.utils.TextInputUtils; // 导入自定义TextInputUtils类,用于处理文本输入相关的实用方法import org.eclipse.paho.client.mqttv3.MqttMessage; // 导入MQTT库中的MqttMessage类,用于表示MQTT消息
import org.json.JSONException; // 导入JSON库中的JSONException类,用于处理JSON解析异常
import org.json.JSONObject; // 导入JSON库中的JSONObject类,用于处理JSON对象import java.util.ArrayList; // 导入Java集合框架中的ArrayList类,用于存储设备列表
import java.util.HashMap; // 导入Java集合框架中的HashMap类,用于存储设备映射
import java.util.Map; // 导入Java集合框架中的Map接口,用于表示键值对的映射关系
/*** 应用的主要活动。包含设置和帮助页面的基本导航。其主要布局包含一个带有所有设备卡的RecyclerView。实时显示所有设备的状态,考虑到网络和偏好更改。还包含一个FAB和用于添加新设备的逻辑。*/
public class MainActivity extends AppCompatActivityimplements View.OnClickListener,SwipeRefreshLayout.OnRefreshListener,Toolbar.OnMenuItemClickListener,ConnectionsHandler {static final String EXTRA_DEV_REMOVED = "com.leondeklerk.smartcontroller.DEV_REMOVED";static final String EXTRA_DEV_CHANGED = "com.leondeklerk.smartcontroller.DEV_CHANGED";static final String EXTRA_PREFS_CHANGED = "com.leondeklerk.smartcontroller.PREFS_CHANGED";private DeviceDialogBinding dialogBinding;private DeviceStorageUtils deviceStorageUtils;private ArrayList<TextInputLayout> layouts;private Map<String, Entry> deviceMap;private MqttClient mqttClient;private NetworkHandler networkHandler;private boolean connected;DeviceAdapter deviceAdapter;Context context;ArrayList<SmartDevice> devices;AlertDialog addDeviceDialog;SharedPreferences preferences;SwipeRefreshLayout refreshLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 注册网络更改处理程序networkHandler = NetworkHandler.getHandler();networkHandler.register(this);networkHandler.setCurrentHandler(this);// 绑定MainActivity布局文件com.leondeklerk.smartcontroller.databinding.ActivityMainBinding binding =ActivityMainBinding.inflate(getLayoutInflater());View view = binding.getRoot();setContentView(view);context = this;preferences = this.getSharedPreferences(getString(R.string.dev_prefs), Context.MODE_PRIVATE);// 获取MQTT客户端。mqttClient = MqttClient.getInstance(this);deviceStorageUtils = new DeviceStorageUtils(preferences, context);deviceMap = new HashMap<>();devices = deviceStorageUtils.getDevices();buildDeviceMap();// 为deviceCards创建一个RecyclerViewRecyclerView recyclerView = binding.deviceList;recyclerView.setHasFixedSize(true);RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);recyclerView.setLayoutManager(layoutManager);deviceAdapter = new DeviceAdapter(devices, this);recyclerView.setAdapter(deviceAdapter);// 设置刷新布局refreshLayout = binding.deviceListRefresh;refreshLayout.setOnRefreshListener(this);binding.toolbar.setOnMenuItemClickListener(this);// 为设备创建的FAB监听器binding.fab.setOnClickListener(v -> {addDeviceDialog = createDeviceDialog();addDeviceDialog.show();Button button = addDeviceDialog.getButton(DialogInterface.BUTTON_POSITIVE);button.setOnClickListener((View.OnClickListener) context);});}@Overridepublic void onDestroy() {super.onDestroy();// 注销处理程序if (networkHandler != null) {Log.d("MainActivity@onDestroy#handler", "unregistered");networkHandler.unregister(this);}// 删除MQTT客户端if (mqttClient != null) {Log.d("MainActivity@onDestroy#client", "unregistered");mqttClient.destroy();}}@Overridepublic void onResume() {super.onResume();networkHandler.setCurrentHandler(this);if (mqttClient.getCurrentHandler() != this) {Log.d("MainActivity@onResume#notThis", "not the current handler");mqttClient.setHandler("MainActivity");}if(!mqttClient.isConnected()) {connected = false;mqttClient = MqttClient.reconnect(this);}pingStatus(-1);}@Overridepublic void onClick(View v) {// 检查是否有任何输入字段出现错误if (!TextInputUtils.hasErrors(layouts)) {// 取消所有任务并关闭对话框addDeviceDialog.dismiss();// 获取SmartDevice的类型int typeId = dialogBinding.newType.getCheckedButtonId();String type = TextInputUtils.DEV_TYPE_DEF;if (typeId == dialogBinding.typeController.getId()) {type = TextInputUtils.DEV_TYPE_RGB;}// 创建新设备并添加SmartDevice device = TextInputUtils.readDevice(context, type, layouts, devices.size());ArrayList<SmartDevice> newList = new ArrayList<>(devices);newList.add(device);updateAdapter(devices, newList);// (重新)构建设备映射buildDeviceMap();// 存储新的设备列表deviceStorageUtils.storeDevices(devices);// 查询新设备的状态pingStatus(devices.size() - 1);}}@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == 0) {// 如果活动正常关闭if (resultCode == RESULT_OK) {int removed = data.getIntExtra(EXTRA_DEV_REMOVED, -1);if (removed >= 0) {updateAdapter(devices, deviceStorageUtils.getDevices());pingStatus(-1);}int changed = data.getIntExtra(EXTRA_DEV_CHANGED, -1);if (changed >= 0) {updateAdapter(devices, deviceStorageUtils.getDevices());pingStatus(changed);}}} else if (requestCode == 1) {// 如果PreferenceActivity正常关闭。if (resultCode == RESULT_OK) {if (data.getBooleanExtra(EXTRA_PREFS_CHANGED, false)) {// 如果首选项更改了,则MqttClient需要重新连接到服务器。connected = false;mqttClient = MqttClient.reconnect(this);}}}}@Overridepublic boolean onMenuItemClick(MenuItem item) {int itemId = item.getItemId();if (itemId == R.id.settings) {// 打开设置屏幕。Intent intent = new Intent(context, SettingsActivity.class);//noinspection deprecationstartActivityForResult(intent, 1);return true;} else if (itemId == R.id.help) {Log.d("MainActivity@onMenuItemClick#help", "Reached help");return true;}return false;}@Overridepublic void onRefresh() {Log.d("MainActivity@onRefresh", "refreshed");// 查询所有设备pingStatus(-1);}@Overridepublic void onMqttMessage(String topic, MqttMessage message) {Log.d("MainActivity@onMqttMessage", "Messaged arrived: " + message.toString());Pair<String, Boolean> parsedTopic = getTopic(topic);if (parsedTopic.second) {Entry entry = deviceMap.get(parsedTopic.first);if (entry != null) {parseResponse(message, entry);}}}@Overridepublic void onMqttSubscribe() {Log.d("MainActivity@onMqttSubscribe", "subscribed");// 设置connected为true并注册处理程序。connected = true;mqttClient.registerHandler("MainActivity", this);mqttClient.setHandler("MainActivity");// 查询所有设备的状态pingStatus(-1);}@Overridepublic void onMqttConnected(boolean connected) {Log.d("MainActivity@onMqttConnected", String.valueOf(connected));// 如果无法建立连接,则通知用户。if (!connected) {Toast.makeText(context,"No connection to the MQTT server (change your preferences?)",Toast.LENGTH_SHORT).show();pingStatus(-1);}}@Overridepublic void onNetworkChange() {Log.d("MainActivity@onNetworkChange", "changed");// 如果网络更改。更改所有设备状态并尝试重新连接MqttClient。resetStatus();connected = false;mqttClient = MqttClient.reconnect(this);}/*** 创建一个对话框,询问用户输入,并为对话框UI注册相关监听器。** @return 用于创建新设备的AlertDialog。*/public AlertDialog createDeviceDialog() {// 根据device_dialog布局创建绑定dialogBinding = DeviceDialogBinding.inflate(LayoutInflater.from(context));// 创建对话框AlertDialog dialog =new MaterialAlertDialogBuilder(context, R.style.MaterialAlertDialog_FilledButtonDialog).setTitle(getString(R.string.add_device_title)).setView(dialogBinding.getRoot()).setPositiveButton(getString(R.string.add_button_confirm), null).setNegativeButton(getString(android.R.string.cancel), null).create();// 将所有TextInputLayout添加到用于错误检查的列表中layouts = new ArrayList<>();layouts.add(dialogBinding.newName);layouts.add(dialogBinding.newTopic);// 注册错误监听器TextInputUtils.setListener(dialogBinding.newName, TextInputUtils.DEFAULT_TYPE);TextInputUtils.setListener(dialogBinding.newTopic, TextInputUtils.DEFAULT_TYPE);return dialog;}/*** 查询设备的状态,如果提供了-1,则将查询所有设备。** @param id 要查询的设备的ID。*/public void pingStatus(int id) {if (connected) {Log.d("MainActivity@pingStatus#if", "connected");if (id >= 0) {devices.get(id).getData().setStatus(getString(R.string.status_unknown));mqttClient.publish(devices.get(id).getPowerStatus());} else {// 查询所有设备for (int i = 0; i < devices.size(); i++) {devices.get(i).getData().setStatus(getString(R.string.status_unknown));mqttClient.publish(devices.get(i).getPowerStatus());}}} else {Log.d("MainActivity@pingStatus#else", "not connected");resetStatus();refreshLayout.setRefreshing(false);}}/*** 计算两个设备列表之间的差异,并将其分发给DeviceAdapter以更新RecyclerView的内容。** @param oldList RecyclerView的当前列表。* @param newList 与之计算差异的新列表。*/public void updateAdapter(ArrayList<SmartDevice> oldList, ArrayList<SmartDevice> newList) {// 计算差异DiffUtilCallback diffUtilCallback = new DiffUtilCallback(oldList, newList);DiffResult diff = DiffUtil.calculateDiff(diffUtilCallback);// 设置设备列表为最新状态devices.clear();devices.addAll(newList);buildDeviceMap();diff.dispatchUpdatesTo(deviceAdapter);}public MqttClient getMqttClient() {return mqttClient;}/*** 从可用设备列表构建设备映射,以主题作为键。*/private void buildDeviceMap() {// 重置当前映射deviceMap.clear();// 用所有条目填充它for (int i = 0; i < devices.size(); i++) {deviceMap.put(devices.get(i).getData().getTopic(), new Entry(i, devices.get(i)));}}/*** 从消息主题的到达消息中提取设备主题。提取的主题用于标识与此消息关联的id和设备。** @param input 消息主题* @return 一个带有设备主题和一个布尔值的对,指示主题是否有效*/private Pair<String, Boolean> getTopic(String input) {String[] split = input.split("/");if (split.length > 2) {int start = split[0].length() + 1;int end = input.length() - split[split.length - 1].length() - 1;return new Pair<>(input.substring(start, end), true);} else {return new Pair<>(null, false);}}/*** 解析消息并处理结果。** @param message 要解析的消息。* @param entry 根据消息更改的条目。*/private void parseResponse(MqttMessage message, Entry entry) {String statusString;try {JSONObject obj = new JSONObject(message.toString());statusString = obj.getString("POWER");} catch (JSONException e) {Log.d("MainActivity@parseErsponse#catch", "not parsable", e);entry.getDevice().getData().setStatus(getString(R.string.status_unknown));e.printStackTrace();return;}// 根据响应设置值if (statusString.equals("ON")) {entry.getDevice().getData().setStatus(getString(R.string.status_on));} else {entry.getDevice().getData().setStatus(getString(R.string.status_off));}refreshLayout.setRefreshing(false);// 更新RecyclerViewdeviceAdapter.notifyItemChanged(entry.getId());}/** 重置所有设备的状态。 */private void resetStatus() {for (SmartDevice device : devices) {device.getData().setStatus(getString(R.string.status_unknown));}}
}
MqttClient.java
/*** 该类创建一个新的MQTT客户端,并处理与此相关的所有连接和回调。将建立与MQTT服务器的连接,可以选择使用SSL。*/
public class MqttClient implements MqttCallback {private static MqttClient INSTANCE;private final MqttAndroidClient client;String serverUri;final String subscriptionTopic = "stat/+/RESULT";private final Map<String, ConnectionsHandler> registeredHandlers;private ConnectionsHandler currentHandler;private final SharedPreferences preferences;private final boolean enableSSL;public ConnectionsHandler getCurrentHandler() {return currentHandler;}/*** 私有构造函数,用于创建Mqtt客户端的实例。只能通过单例方法进行实例化。** @param context 客户端将在其中运行的上下文。*/private MqttClient(Context context) {preferences = PreferenceManager.getDefaultSharedPreferences(context);// 检查SSL是否打开,并调整URL格式。enableSSL = preferences.getBoolean("mqtt_ssl", false);String urlTemplate = "tcp://%s:%s";if (enableSSL) {urlTemplate = "ssl://%s:%s";}serverUri =String.format(urlTemplate,preferences.getString("mqtt_ip", "localhost"),Integer.parseInt(preferences.getString("mqtt_port", "8883")));// 创建一个新的客户端client =new MqttAndroidClient(context, serverUri, org.eclipse.paho.client.mqttv3.MqttClient.generateClientId());registeredHandlers = new HashMap<>();currentHandler = (ConnectionsHandler) context;connect();}/*** 设置连接选项,注册处理程序和缓冲选项,然后进行连接。*/private void connect() {// 创建连接选项MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();mqttConnectOptions.setAutomaticReconnect(false);mqttConnectOptions.setCleanSession(false);mqttConnectOptions.setUserName(preferences.getString("mqtt_username", "admin"));mqttConnectOptions.setPassword(preferences.getString("mqtt_password", "admin").toCharArray());// 如果SSL设置已启用,请确保设置了自定义CA文件(如果适用)。if (enableSSL) {setSSLOption(mqttConnectOptions);}try {client.connect(mqttConnectOptions,null,new IMqttActionListener() {@Overridepublic void onSuccess(IMqttToken asyncActionToken) {Log.d("MqttClient@connect#onSuccess", "Connected to: " + serverUri);DisconnectedBufferOptions disconnectedBufferOptions = new DisconnectedBufferOptions();disconnectedBufferOptions.setBufferEnabled(false);disconnectedBufferOptions.setBufferSize(100);disconnectedBufferOptions.setPersistBuffer(false);disconnectedBufferOptions.setDeleteOldestMessages(false);client.setBufferOpts(disconnectedBufferOptions);currentHandler.onMqttConnected(true);subscribeToTopic();}@Overridepublic void onFailure(IMqttToken asyncActionToken, Throwable exception) {Log.d("MqttClient@connect#onFailure","Failed to connect to: " + serverUri + exception.toString(), exception);currentHandler.onMqttConnected(false);}});} catch (Exception ex) {Log.d("MqttClient@connect#catch2", "Error while connecting", ex);}}/*** 注册将处理客户端发出的不同操作的处理程序。** @param key 要使用的处理程序的键。*/public void setHandler(String key) {ConnectionsHandler handler = registeredHandlers.get(key);if (handler != null) {Log.d("MqttClient@setHandler#notNull", key);currentHandler = handler;}}/*** 设置客户端的回调。*/public void setCallback() {client.setCallback(this);}/*** 如果在设置中启用了SSL,请确保如果添加了证书,则将其添加到Android密钥库中。如果未设置SSL证书,将使用默认的Android证书验证连接。** @param options 添加SSL工厂的选项。*/private void setSSLOption(MqttConnectOptions options) {try {CertificateFactory cf = CertificateFactory.getInstance("X.509");String certString = preferences.getString("mqtt_cert", null);if (certString == null) return;InputStream caInput = new ByteArrayInputStream(certString.getBytes());Certificate ca;try {ca = cf.generateCertificate(caInput);} catch (CertificateException ex) {Log.d("MqttClient@setSSLOption#generateCertifcate#catch", "Incorrect certificate format", ex);return;} finally {caInput.close();}// 创建包含我们受信任CA的KeyStoreString keyStoreType = KeyStore.getDefaultType();KeyStore keyStore = KeyStore.getInstance(keyStoreType);keyStore.load(null, null);keyStore.setCertificateEntry("ca", ca);// 创建信任我们KeyStore中CA的TrustManagerString tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);tmf.init(keyStore);// 创建使用我们的TrustManager的SSLContextSSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(null, tmf.getTrustManagers(), null);options.setSocketFactory(sslContext.getSocketFactory());} catch (Exception ex) {Log.d("MqttClient@connect#catch", "Error while setting the certificate", ex);}}/*** 订阅MQTT主题并注册处理程序。*/private void subscribeToTopic() {try {client.subscribe(subscriptionTopic,0,null,new IMqttActionListener() {@Overridepublic void onSuccess(IMqttToken asyncActionToken) {Log.d("MqttClient@subscribeToTopic#onSuccess", "Subscribed!");setCallback();currentHandler.onMqttSubscribe();}@Overridepublic void onFailure(IMqttToken asyncActionToken, Throwable exception) {Log.d("MqttClient@subscribeToTopic#onFailure", "Subscribed fail", exception);}});} catch (Exception ex) {Log.d("MqttClient@subscribeToTopic#catch", "Error while subscribing", ex);}}/*** 销毁MqttClient并确保它断开连接。*/public void destroy() {try {if (client != null) {if (client.isConnected()) {client.disconnect();}}Log.d("MqttClient@destroy#try", "Client destroyed");} catch (Exception e) {Log.d("MqttClient@destroy#catch", "Error while destroying", e);}}/*** 发布命令到MQTT代理** @param command 包含主题和值的要发布的命令。*/public void publish(Command command) {try {MqttMessage message = new MqttMessage();message.setPayload(command.getMessage().getBytes());client.publish(command.getTopic(), message);Log.d("MqttClient@publish#try", command.getMessage());} catch (Exception e) {Log.d("MqttClient@publish#catch", "Error while publishing", e);}}/*** 注册客户端可以切换到的新的ConnectionsHandler。** @param key        用于标识处理程序的键。* @param newHandler 需要注册的新处理程序。*/public void registerHandler(String key, ConnectionsHandler newHandler) {Log.d("MqttClient@registerHandler", key);registeredHandlers.put(key, newHandler);}/*** 获取客户端的(新)实例。** @param context 需要注册的上下文。* @return MqttClient的(新)实例*/public static MqttClient getInstance(Context context) {if (INSTANCE == null) {Log.d("MqttClient@getInstance", "null");INSTANCE = new MqttClient(context);}return INSTANCE;}public static MqttClient reconnect(Context context) {Log.d("MqttClient@reconnect", "Reconnecting");INSTANCE.destroy();INSTANCE = null;return getInstance(context);}@Overridepublic void connectionLost(Throwable cause) {Log.d("MqttClient@connectionLost", "Connection lost", cause);}@Overridepublic void messageArrived(String topic, MqttMessage message) {Log.d("MqttClient@messageArrived", message.toString());currentHandler.onMqttMessage(topic, message);}@Overridepublic void deliveryComplete(IMqttDeliveryToken token) {Log.d("MqttClient@deliveryComplete", "Delivered");}public boolean isConnected() {return client.isConnected();}
}
NetworkHandler.java
/*** 处理网络变化的类。如果设备连接到新的网络,将使用OnAvailable方法,结合ConnectionsHandler,对状态变化进行操作。*/
public class NetworkHandler extends NetworkCallback {private static NetworkHandler INSTANCE;private static int count = 0;private ConnectionsHandler currentHandler;/*** 私有构造函数。*/private NetworkHandler() {}public ConnectionsHandler getCurrentHandler() {return currentHandler;}public void setCurrentHandler(ConnectionsHandler currentHandler) {this.currentHandler = currentHandler;}@Overridepublic void onAvailable(@NotNull Network network) {// 确保第一次网络变化不做任何事情(应用程序启动)if (count > 1) {Log.d("NetworkHandler@onAvailable#if", "Bigger");// 在此执行所需的操作if (currentHandler != null) {currentHandler.onNetworkChange();} else {Log.d("NetworkHandler@onAvailable#if#else", "no handler");}} else {Log.d("NetworkHandler@onAvailable#else", "smaller");}count++;}/*** 为该应用程序注册NetworkHandler。** @param context 从中检索ConnectivityManager的上下文。*/public void register(Context context) {NetworkRequest request =new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();ConnectivityManager connectivityManager =(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);if (connectivityManager != null) {connectivityManager.registerNetworkCallback(request, this);Log.d("NetworkHandler@register#notNull", "callback registered");} else {Log.d("NetworkHandler@register#null", "Manager null");}}/*** 为该应用程序取消注册处理程序。** @param context 从中检索ConnectivityManager的上下文。*/public void unregister(Context context) {ConnectivityManager connectivityManager =(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);if (connectivityManager != null) {Log.d("NetworkHandler@unregister#notNull", "callback unregistered");connectivityManager.unregisterNetworkCallback(this);} else {Log.d("NetworkHandler@unregister#null", "callback not unregistered");}}/*** 获取NetworkHandler的实例,如果尚未存在,则创建一个新实例。** @return 处理程序的实例。*/public static NetworkHandler getHandler() {Log.d("NetworkHandler@getHandler", "Handler requested");if (INSTANCE == null) {INSTANCE = new NetworkHandler();}return INSTANCE;}
}
SettingsActivity.java
/*** 包含应用程序所有设置的活动。主要用于MQTT服务器设置。*/
public class SettingsActivity extends AppCompatActivity implementsOnSharedPreferenceChangeListener {private Intent result;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Inflate the view binding.SettingsActivityBinding binding = SettingsActivityBinding.inflate(getLayoutInflater());View view = binding.getRoot();setContentView(view);getSupportFragmentManager().beginTransaction().replace(R.id.settings, new SettingsFragment(this)).commit();// 设置工具栏binding.toolbar.setTitle(getString(R.string.title_activity_settings));binding.toolbar.setNavigationOnClickListener(view1 -> onBackPressed());PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);}@Overridepublic void onDestroy() {super.onDestroy();if (result == null) {result = new Intent();result.putExtra(MainActivity.EXTRA_PREFS_CHANGED, false);setResult(Activity.RESULT_OK, result);}}@Overridepublic void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {// 如果偏好发生变化,通知调用的Activity。result = new Intent();result.putExtra(MainActivity.EXTRA_PREFS_CHANGED, true);setResult(Activity.RESULT_OK, result);}/*** 实际偏好的片段。*/public static class SettingsFragment extends PreferenceFragmentCompat implementsOnPreferenceClickListener {private SharedPreferences preferences;private final static int OPEN_FILE_PICKER = 1;private Preference filePickerPreference;private final Context context;/*** 默认构造函数,用于接收上下文。** @param context 应用程序的上下文*/SettingsFragment(Context context) {this.context = context;}@SuppressLint("ClickableViewAccessibility")@Overridepublic void onCreatePreferences(Bundle savedInstanceState, String rootKey) {preferences = PreferenceManager.getDefaultSharedPreferences(context);setPreferencesFromResource(R.xml.root_preferences, rootKey);// 查找文件选择器偏好并设置摘要和单击侦听器。filePickerPreference = findPreference("filePicker");if (filePickerPreference != null) {filePickerPreference.setOnPreferenceClickListener(this);filePickerPreference.setSummary(preferences.getString("mqtt_file_picker_summary", ""));}final EditTextPreference preference = findPreference("mqtt_password");// 用星号替换密码字段的值,以增强安全性。// 基于: https://stackoverflow.com/a/59072162/8298898if (preference != null) {preference.setSummaryProvider(preference12 -> {// 检查是否有值String getPassword = PreferenceManager.getDefaultSharedPreferences(requireContext()).getString("mqtt_password", "Not set");// 返回“not set”否则返回带星号的密码if (getPassword.equals("not set")) {return getPassword;} else {return (setAsterisks(getPassword.length()));}});// 将密码字段的值替换为星号,并将摘要设置为带星号的新密码preference.setOnBindEditTextListener(editText -> {editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);Drawable visibilityDrawable = ContextCompat.getDrawable(context, R.drawable.baseline_visibility_24);editText.setCompoundDrawablesWithIntrinsicBounds(null, null, visibilityDrawable, null);editText.setOnTouchListener((view, motionEvent) -> {if (motionEvent.getAction() == MotionEvent.ACTION_UP) {if (motionEvent.getRawX() >= (editText.getRight() - editText.getCompoundDrawables()[2].getBounds().width())) {if (editText.getInputType() == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) {editText.setInputType(InputType.TYPE_CLASS_TEXT);Drawable draw = ContextCompat.getDrawable(context, R.drawable.baseline_visibility_off_24);editText.setCompoundDrawablesWithIntrinsicBounds(null, null, draw, null);} else {editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);Drawable draw = ContextCompat.getDrawable(context, R.drawable.baseline_visibility_24);editText.setCompoundDrawablesWithIntrinsicBounds(null, null, draw, null);}return true;}}return false;});preference.setSummaryProvider(preference1 -> setAsterisks(editText.getText().toString().length()));});}}/*** 根据长度创建一串星号。** @param length 输入字符串的长度* @return 星号字符串*/private String setAsterisks(int length) {StringBuilder sb = new StringBuilder();for (int s = 0; s < length; s++) {sb.append("*");}return sb.toString();}@Overridepublic boolean onPreferenceClick(@NonNull Preference preference) {// 创建一个打开文件选择器的意图Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("*/*");String[] mimetypes = {"application/x-pem-file", "application/x-x509-ca-cert", "application/pkix-cert"};intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes);// 启动文件选择器//noinspection deprecationstartActivityForResult(intent, OPEN_FILE_PICKER);return true;}@SuppressWarnings("deprecation")@Overridepublic void onActivityResult(int requestCode, int resultCode,Intent resultData) {// 完成文件选择器后if (requestCode == OPEN_FILE_PICKER && resultCode == Activity.RESULT_OK) {if (resultData != null) {// 所选文件的URIUri uri = resultData.getData();// 获取文件的名称Cursor cursor = requireContext().getContentResolver().query(uri, null, null, null, null);int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);cursor.moveToFirst();String fileName = cursor.getString(nameIndex);cursor.close();// 将文件的名称设置为摘要并保存preferences.edit().putString("mqtt_file_picker_summary", fileName).apply();filePickerPreference.setSummary(fileName);// 根据URI读取文件try {String text = readTextFromUri(uri);// 粗略证书验证if (text.startsWith("-----BEGIN CERTIFICATE-----") && text.endsWith("-----END CERTIFICATE-----\n")) {preferences.edit().putString("mqtt_cert", text).apply();} else {Toast.makeText(getContext(), "Invalid file", Toast.LENGTH_SHORT).show();}} catch (IOException e) {Log.d("SettingsActiviy@onActivityResult#catch", "Reading failed", e);}}} else {Toast.makeText(getContext(), "No file selected", Toast.LENGTH_SHORT).show();}}/*** 根据URI读取文件。** @param uri 要查找文件的内容URI* @return 文件内的文本* @throws IOException 在读取文件时抛出的错误。*/private String readTextFromUri(Uri uri) throws IOException {StringBuilder stringBuilder = new StringBuilder();try (// 打开URIInputStream inputStream = requireContext().getContentResolver().openInputStream(uri);// 从输入流创建读取器BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(inputStream)))) {int charInt;// 当有字符要读取时while ((charInt = reader.read()) != -1) {char character = (char) charInt;// 过滤掉潜在有害的字符(不应出现在证书中)if (character == '(' || character == '{') {continue;}stringBuilder.append((char) charInt);}}return stringBuilder.toString();}}
}

“main/res”里的xml代码

关于res:在 Android 开发中,res 文件夹是 "resources"(资源)的缩写,它是 Android 应用项目中存放各种资源文件的目录之一。这个目录主要包含了应用在运行时使用的非代码资源,例如图像、布局文件、字符串、颜色等。res 文件夹通常在应用的 app 模块下,是 Android 项目的标准结构之一。

anim: 包含动画资源文件,用于定义应用中的动画效果。

drawable: 存放应用图标、图片等可绘制资源。(就是一些矢量图)

layout: 包含应用中的布局文件,用于定义用户界面的结构和外观。

menu:在这个项目中定义应用右上角的弹出菜单。

mipmap: 存放应用图标的不同分辨率版本,用于适配不同屏幕密度的设备。

values: 包含了资源文件,如字符串、颜色、尺寸等,这些资源可以在应用的代码和布局文件中引用。

xml: 用于存放一些 XML 格式的资源文件,如菜单文件、布局文件引用等。(在这个项目中存放了用于配置MQTT的相关参数)

另外如果后续有需要的话还有raw,raw存放原始资源文件,例如音频或视频文件,这些文件在运行时不会被编译成资源 ID。

如何导出指定文件夹下所有文件名称(包括所有子代)

java和kotlin的区别,在该项目中哪里分别使用了什么

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/680784.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB Coder从入门到放弃

一、MATLAB Coder入门 1 MATLAB Coder是什么 从 MATLAB 代码生成 C 和 C 代码 MATLAB Coder™ 可从 MATLAB 代码生成适用于各种硬件平台&#xff08;从桌面计算机系统到嵌入式硬件&#xff09;的 C 和 C 代码。它支持大多数 MATLAB 语言和广泛的工具箱。您可以将生成的代码作…

Android14音频进阶:MediaPlayerService如何启动AudioTrack 下篇(五十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

比较6*6范围内8个点425个结构的顺序

( A, B )---6*30*2---( 1, 0 )( 0, 1 ) 让网络的输入有6个节点&#xff0c;训练集AB各由6张二值化的图片组成&#xff0c;让A中有8个点&#xff0c;让B全是0&#xff0c;收敛误差7e-4&#xff0c;收敛199次&#xff0c;统计迭代次数平均值并排序。 假设这个6*6的结构的行和列都…

C++进阶(十六)特殊类设计

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、请设计一个类&#xff0c;不能被拷贝二、请设计一个类&#xff0c;只能在堆上创建对象三、…

力扣1122. 数组的相对排序(哈希表)

Problem: 1122. 数组的相对排序 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.利用arr2创建一个无序映射&#xff08;map集合&#xff09;&#xff0c;以其中的元素作为键&#xff0c;值默认设置为0&#xff1b; 2.扫描arr1数组统计arr2元素在其中的个数(将个…

单调队列优化DP问题

目录 1.滑动窗口 2.最大子序和 3.旅行问题 4.烽火传递 5.绿色通道 6.修剪草坪 7.理想的正方形 1.滑动窗口 154.给定一个大小为 n≤106 的数组。 有一个大小为 k 的滑动窗口&#xff0c;它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向…

游泳时可以听歌的耳机有哪些?戴游泳耳机有哪些好处?

游泳和跑步在某种程度上相似&#xff0c;特别是在短距离冲刺时&#xff0c;大脑似乎变得空白&#xff0c;而在中长距离的有氧运动中&#xff0c;身体感到疲劳&#xff0c;但大脑却异常清晰&#xff0c;时间却显得格外漫长。如何打发时间&#xff0c;让游泳锻炼变得不无聊&#…

力扣面试题 16.21. 交换和(哈希表)

Problem: 面试题 16.21. 交换和 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.分别求取array1与array2数组每一个元素的和&#xff08;sum1与sum2&#xff09;并同时将array2的元素存入一个set集合中&#xff1b; 2.如果sum1和sum2的和为奇数&#xff0c;则不…

使用 Windows 11/10 上的最佳 PDF 转 Word 转换器释放 PDF 的潜力

毫无疑问&#xff0c;PDF 是最好的文档格式之一&#xff0c;但就像其他格式一样&#xff0c;有时它们确实会带来一些限制。例如&#xff0c;在某些情况下&#xff0c;您可能想要将 PDF 转换为 Word。在这种情况下&#xff0c;您始终可以借助 PDF 到 Word 转换器的帮助。 为了说…

Java实现软件学院思政案例库系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理员2.2 普通教师 三、系统展示四、核心代码4.1 查询思政案例4.2 审核思政案例4.3 查询思政课程4.4 思政案例点赞4.5 新增思政案例评语 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的软件学…

谁再用Arrays.asList就开除谁

谁再用Arrays.asList就开除谁 hi&#xff0c;我是achang&#xff0c;今天说一个使用Arrays.asList后对应出现的一系列坑&#xff0c;因为他有那么多坑&#xff0c;所以会有开玩笑的说&#xff1a;谁再用Arrays.asList就开除谁 那Arrays.asList的作用很简单&#xff0c;就是把…

86.分布式锁理论分析

文章目录 前言一、为什么需要分布式锁&#xff1f;二、基于 Redis 分布式锁怎么实现&#xff1f;三、Redis 分布锁存在的问题3.1 死锁问题3.2 锁过期时间问题3.3 锁被别人释放问题 四、Redis 分布锁小结五、Redis 主从同步对分布式锁的影响六、Redlock 方案七、Redlock 的争论7…

autojs通过正则表达式获取带有数字的text内容

var ctextMatches(/\d/).findOne()console.log("当前金币"c.text()) // 获取当前金币UiSelector.textMatches(reg) reg {string} | {Regex} 要满足的正则表达式。 为当前选择器附加控件"text需要满足正则表达式reg"的条件。 有关正则表达式&#xff0c;可…

揭秘外观模式:简化复杂系统的关键设计策略

前言 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它隐藏了系统的复杂性&#xff0c;并向客户端提供了一个可以访问系统的接口。这种类型的设计模式向现有的系统添加一个接口&#xff0c;来隐藏系统的复杂性。这种模式涉及到一个单一的类…

【C语言】实现单链表

目录 &#xff08;一&#xff09;头文件 &#xff08;二&#xff09;功能实现 &#xff08;1&#xff09;打印单链表 &#xff08;2&#xff09;头插与头删 &#xff08;3&#xff09;尾插与尾删 &#xff08;4&#xff09; 删除指定位置节点 和 删除指定位置之后的节点 …

蓝桥杯嵌入式第9届真题(完成) STM32G431

蓝桥杯嵌入式第9届真题(完成) STM32G431 题目 分析和代码 main.h /* USER CODE BEGIN Header */ /********************************************************************************* file : main.h* brief : Header for main.c file.* …

Java-并发高频面试题-2

接着之前的Java-并发高频面试题 7. synchronized的实现原理是怎么样的&#xff1f; 首先我们要知道synchronized它是解决线程安全问题的一种方式&#xff0c;而具体是怎么解决的呢&#xff1f;主要是通过加锁的方式来解决 在底层实现上来看 是通过 monitorenter、monitorexit…

【Spring原理进阶】SpringMVC调用链+JSP模板应用讲解

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Spring 狂野之旅&#xff1a;底层原理高级进阶》 &#x1f680…

【Python网络编程之Ping命令的实现】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python开发技术 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Python网络编程之Ping命令的实现 代码见资源&#xff0c;效果图如下一、实验要求二、协议原理2…

所谓的意志力,也许根本就不存在

许多讲自我提升的书&#xff0c;往往会把成功的原因归结为两点&#xff1a;自律&#xff0c;以及专注。 他们会告诉你&#xff1a;为什么别人能够成功、而你不能&#xff1f;第一是你不够自律&#xff0c;无法每天雷打不动地坚持「好习惯」&#xff1b;第二&#xff0c;是你不够…