Android消息推送MQTT实战

2023-05-16

1 前言

年初做了一款Android TV 应用,用到了MQTT。主要实现的是类似一些景区利用大屏幕实时显示景点人数,超过人数就不允许进入。即利用闸机设备监控到进景区的游客,然后通过MQTT将消息发送给大屏幕,最后大屏幕实时显示景区人数,并响应一个消息通知闸机设备已经收到了它发过来的消息(确保消息到达)。这篇文章会模拟真实的使用流程进行讲解,即闸机发布消息——服务器(代理)收到消息转发给大屏幕——大屏幕收到消息后响应回去(发布消息)——服务器收到消息转发给闸机设备。

2 关于MQTT

2.1 简介

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议。它是一种发布/订阅,极其简单和轻量级的消息传递协议,专为受限设备和低带宽,高延迟或不可靠的网络而设计。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境。相对于XMPP,MQTT更加轻量级,并且占用的宽带低。

2.2 特点

MQTT协议有以下特点:

  1. 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
  2. 对负载内容屏蔽的消息传输。
  3. 使用 TCP/IP 提供网络连接。
  4. 有三种消息发布服务质量:
    • qos为0:“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
    • qos为1:“至少一次”,确保消息到达,但消息重复可能会发生。这一级别可用于如下情况,你需要获得每一条消息,并且消息重复发送对你的使用场景无影响。
    • qos为2:“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
  5. 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量。
    使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。

2.3 MQTT体系结构

该体系结构图是结合文章开头说的例子画出来的,能很好的描述MQTT在实际运用中的三种身份。即进景区入口配置一台闸机设备作为发布者(Publisher),当闸机设备监控到有游客进入的时候会发布一个带主题(Topic)的消息(例如主题为“tourist_enter”)给服务器(MQTT-Broker),当服务器接收到发布过来的消息后,会进行基于主题的过滤,将消息转发给订阅了该主题的订阅者。 而景区大屏幕作为订阅者(Subscriber),订阅的主题也是“tourist_enter”,这样就能接收到服务器转发过来的消息,收到消息后在大屏幕上实时显示当前景区人数即可。

该结构图中的闸机设备和大屏幕都是客户端,都可以进行发布和订阅。例如大屏幕收到消息后也可以发布一个消息通知闸机设备已经收到了它发过来的消息。

3 MQTT服务器搭建

想要使用MQTT,首先需要搭建一个MQTT的服务器(在公司一般是后台人员负责搭建)。一般前端人员为了方便测试都会先使用第三方提供的服务器,官方推荐了很多种服务器,我这里选用的是Apollo(属于Apache ActiveMQ)。

1. 下载、解压

点击下载地址,选择最适合你的操作系统的版本进行下载,我这里用的是Windows,进行如下选择:

下载后进行解压,我这里解压到D盘根目录下(D:\apache-apollo-1.7.1)。

2. 创建服务器实例

命令行进入解压文件的bin目录下(例如:cd D:\apache-apollo-1.7.1\bin),然后输入apollo create mybroker(其中mybroker为自定义的服务器名称)创建服务器实例。具体如下图:

之后会在bin目录下生成mybroker文件夹,其中mybroker文件夹下的etc\apollo.xml文件下是配置服务器信息的文件,etc\users.properties文件包含连接MQTT服务器时用到的用户名和密码,注意这里只能修改密码(发现很多博客在没有验证的情况下就说用户名和密码都在这里修改),如果要修改用户名需要到etc\groups.properties文件下去修改。etc\groups.properties文件下的用户名与etc\users.properties文件下的密码是一一对应的,如下表示一个组中配置了两个用户分别是admin与wildma,然后这两个用户名对应的密码分别是password与123456

3. 开启服务器

进入mybroker文件夹下的bin目录下,输入apollo-broker.cmd run开启服务器。看到如下界面表示开启成功。

4. 验证是否安装成功

最后在浏览器输入http://127.0.0.1:61680/,能成功打开界面就表示安装成功了。可以用上面配置的两个用户名进行登录。

4 调试MQTT的客户端——mqttfx 的使用

为了方便调试MQTT,我这里选用mqttfx作为闸机设备客户端。具体使用如下:

  1. 下载
    点击下载地址,选择最适合你的操作系统的版本进行下载。如下图:

  2. 安装

    下载后一路点击下一步即可安装成功,安装成功后打开软件界面。如下图:

  3. 配置

    点击上图中的设置,添加一个新的配置文件。分别填写配置文件名称、服务器地址(由于服务器就是本机,所以这里用本机的IP地址即可,ipconfig/all可获取IP地址)、端口号(开启服务器后会显示接受连接的地址:Accepting connections at: tcp://0.0.0.0:61613,用这里的端口号61613即可,见上文中“开启服务器”后的图片)、用户名、密码,点击OK即可。如下图:

  4. 订阅消息

    选择刚刚添加的配置文件“闸机设备”,点击"Connect"连接服务器。点击“Subscribe”,设置一个Topic(例如tourist_enter),点击Topic右侧的“Subscribe”进行消息订阅。如下图:

  5. 发布消息

    点击“Publish”,输入刚刚订阅的Topic (tourist_enter),输入需要发布的消息内容(tourist enter),点击Topic右侧的“Publish”进行消息发布。如下图:

    再返回订阅界面就能看到刚刚发布的消息,如下图:

5 Android中MQTT的使用

Android中使用MQTT需要使用到Paho Android Service库,Paho Android Service是一个用Java编写的MQTT客户端库。
GitHub地址:https://github.com/eclipse/paho.mqtt.android

5.1 集成

  1. 在module的build.gradle文件中添加依赖
repositories {
    maven {
        url "https://repo.eclipse.org/content/repositories/paho-snapshots/"
    }
}
dependencies {
    compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
    compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
}
  1. 在 AndroidManifest.xml 添加限权
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
  1. 在 AndroidManifest.xml 注册Service (MyMqttService为自己写的服务,下文会讲到)
        <service android:name="org.eclipse.paho.android.service.MqttService" /> <!--MqttService-->
        <service android:name="com.dongyk.service.MyMqttService"/> <!--MyMqttService-->

5.2 具体代码

5.2.1 Android中使用MQTT最主要的就是以下几个方法:

  • connect:连接MQTT服务器,这里主要讲3个参数的方法,如下:
 @Override
 public IMqttToken connect(MqttConnectOptions options, Object userContext,
   IMqttActionListener callback) throws MqttException {
    //...
}

参数options:用来携带连接服务器的一系列参数,例如用户名、密码等。
参数userContext:可选对象,用于向回调传递上下文。一般传null即可。
参数callback:用来监听MQTT是否连接成功的回调

  • publish:发布消息,这里使用四个参数的方法,如下:
 @Override
 public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
   boolean retained) throws MqttException, MqttPersistenceException {
    //...
 }

参数topic:发布消息的主题
参数payload:消息的字节数组
参数qos:提供消息的服务质量,可传0、1或2
参数retained:是否在服务器保留断开连接后的最后一条消息

  • subscribe:订阅消息,这里主要讲2个参数的方法,如下:
 @Override
 public IMqttToken subscribe(String topic, int qos) throws MqttException,
   MqttSecurityException {
    //...
 }

参数topic:订阅消息的主题
参数qos:订阅消息的服务质量,可传0、1或2

5.2.2 MQTT服务——MyMqttService

下面写一个 Service 来实现MQTT在Android运用中的connect、publish、subscribe

package com.wildma.mqttandroidclient;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.widget.Toast;

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;

/**
 * Author       wildma
 * Github       https://github.com/wildma
 * CreateDate   2018/11/08
 * Desc         ${MQTT服务}
 */

public class MyMqttService extends Service {

    public final String TAG = MyMqttService.class.getSimpleName();
    private static MqttAndroidClient  mqttAndroidClient;
    private        MqttConnectOptions mMqttConnectOptions;
    public        String HOST           = "tcp://192.168.0.102:61613";//服务器地址(协议+地址+端口号)
    public        String USERNAME       = "admin";//用户名
    public        String PASSWORD       = "password";//密码
    public static String PUBLISH_TOPIC  = "tourist_enter";//发布主题
    public static String RESPONSE_TOPIC = "message_arrived";//响应主题
    @RequiresApi(api = 26)
    public        String CLIENTID       = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
            ? Build.getSerial() : Build.SERIAL;//客户端ID,一般以客户端唯一标识符表示,这里用设备序列号表示

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        init();
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 开启服务
     */
    public static void startService(Context mContext) {
        mContext.startService(new Intent(mContext, MyMqttService.class));
    }

    /**
     * 发布 (模拟其他客户端发布消息)
     *
     * @param message 消息
     */
    public static void publish(String message) {
        String topic = PUBLISH_TOPIC;
        Integer qos = 2;
        Boolean retained = false;
        try {
            //参数分别为:主题、消息的字节数组、服务质量、是否在服务器保留断开连接后的最后一条消息
            mqttAndroidClient.publish(topic, message.getBytes(), qos.intValue(), retained.booleanValue());
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    /**
     * 响应 (收到其他客户端的消息后,响应给对方告知消息已到达或者消息有问题等)
     *
     * @param message 消息
     */
    public void response(String message) {
        String topic = RESPONSE_TOPIC;
        Integer qos = 2;
        Boolean retained = false;
        try {
            //参数分别为:主题、消息的字节数组、服务质量、是否在服务器保留断开连接后的最后一条消息
            mqttAndroidClient.publish(topic, message.getBytes(), qos.intValue(), retained.booleanValue());
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化
     */
    private void init() {
        String serverURI = HOST; //服务器地址(协议+地址+端口号)
        mqttAndroidClient = new MqttAndroidClient(this, serverURI, CLIENTID);
        mqttAndroidClient.setCallback(mqttCallback); //设置监听订阅消息的回调
        mMqttConnectOptions = new MqttConnectOptions();
        mMqttConnectOptions.setCleanSession(true); //设置是否清除缓存
        mMqttConnectOptions.setConnectionTimeout(10); //设置超时时间,单位:秒
        mMqttConnectOptions.setKeepAliveInterval(20); //设置心跳包发送间隔,单位:秒
        mMqttConnectOptions.setUserName(USERNAME); //设置用户名
        mMqttConnectOptions.setPassword(PASSWORD.toCharArray()); //设置密码

        // last will message
        boolean doConnect = true;
        String message = "{\"terminal_uid\":\"" + CLIENTID + "\"}";
        String topic = PUBLISH_TOPIC;
        Integer qos = 2;
        Boolean retained = false;
        if ((!message.equals("")) || (!topic.equals(""))) {
            // 最后的遗嘱
            try {
                mMqttConnectOptions.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) {
            doClientConnection();
        }
    }

    /**
     * 连接MQTT服务器
     */
    private void doClientConnection() {
        if (!mqttAndroidClient.isConnected() && isConnectIsNomarl()) {
            try {
                mqttAndroidClient.connect(mMqttConnectOptions, null, iMqttActionListener);
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 判断网络是否连接
     */
    private boolean isConnectIsNomarl() {
        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, "当前网络名称:" + name);
            return true;
        } else {
            Log.i(TAG, "没有可用网络");
            /*没有可用网络的时候,延迟3秒再尝试重连*/
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    doClientConnection();
                }
            }, 3000);
            return false;
        }
    }

    //MQTT是否连接成功的监听
    private IMqttActionListener iMqttActionListener = new IMqttActionListener() {

        @Override
        public void onSuccess(IMqttToken arg0) {
            Log.i(TAG, "连接成功 ");
            try {
                mqttAndroidClient.subscribe(PUBLISH_TOPIC, 2);//订阅主题,参数:主题、服务质量
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onFailure(IMqttToken arg0, Throwable arg1) {
            arg1.printStackTrace();
            Log.i(TAG, "连接失败 ");
            doClientConnection();//连接失败,重连(可关闭服务器进行模拟)
        }
    };

    //订阅主题的回调
    private MqttCallback mqttCallback = new MqttCallback() {

        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
            Log.i(TAG, "收到消息: " + new String(message.getPayload()));
            //收到消息,这里弹出Toast表示。如果需要更新UI,可以使用广播或者EventBus进行发送
            Toast.makeText(getApplicationContext(), "messageArrived: " + new String(message.getPayload()), Toast.LENGTH_LONG).show();
            //收到其他客户端的消息后,响应给对方告知消息已到达或者消息有问题等
            response("message arrived");
        }

        @Override
        public void deliveryComplete(IMqttDeliveryToken arg0) {

        }

        @Override
        public void connectionLost(Throwable arg0) {
            Log.i(TAG, "连接断开 ");
            doClientConnection();//连接断开,重连
        }
    };

    @Override
    public void onDestroy() {
        try {
            mqttAndroidClient.disconnect(); //断开连接
        } catch (MqttException e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }
}

该 MyMqttService 类的大概逻辑就是开启服务后,调用init()方法初始化各个参数,包括服务器地址、用户名、密码等等,然后调用doClientConnection()方法连接MQTT服务器,iMqttActionListener用来监听MQTT是否连接成功,连接成功则订阅主题。mqttCallback为订阅主题的回调,收到消息后会执行该回调中的messageArrived()方法,拿到消息后进行UI更新,并调用response()方法响应给对方告知消息已到达或者消息有问题等。

5.2.3 开启服务

在MainActivity中开启服务,这里为了方便不做UI更新,所以就一行开启服务的代码,如下:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class MainActivity extends AppCompatActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       MyMqttService.startService(this); //开启服务
   }
}

6 模拟真实场景

还是以文章开头说的例子来讲,现在拿mqttfx客户端作为闸机设备,上面的Android代码运行后作为大屏幕。

  1. 将大屏幕与服务器连接
    即将大屏幕APK运行到Android TV上,没有TV可以用Android手机代替。记得代码中的发布主题设置为“tourist_enter”,响应主题设置为“message_arrived”。

  2. 将闸机设备与服务器连接

    选择闸机设备——点击连接——发布主题设置为“tourist_enter”,如下图:


    切换到Subscribe界面——响应主题设置为“message_arrived”——点击Subscribe按钮进行订阅,如下图:

  3. 发布
    点击步骤2图中的Publish按钮进行发布

  4. 大屏幕收到消息
    这时候大屏幕收到服务器转发过来的消息,就会在大屏幕上显示进场人数,并响应给对方告知消息已到达。代码中为了简单就弹个Toast表示,具体显示就不贴图了。

  5. 闸机设备收到消息这时候mqttfx切换到Subscribe界面就可以看到大屏幕响应回来的消息,如下:

如上流程就是大概模拟我在开发中用到的MQTT使用流程,当然我的真实项目并没有那么简单,还包括各种数据和UI交互显示。希望模拟这种真实的使用流程进行讲解能让各位更好的理解MQTT的使用,有不足的请指出。

项目地址:MqttAndroidClient

参考资料:

  • MQTT 101 – How to Get Started with the lightweight IoT Protocol
  • MQTT Client Library Encyclopedia – Paho Android Service
  • Android APP必备高级功能,消息推送之MQTT


转载于:https://www.jianshu.com/p/73436a5cf855
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android消息推送MQTT实战 的相关文章

  • 使用 HttpClient 时,为什么服务器响应中省略了 Content-Length 标头?

    我正在使用这个问题的源代码如何异步执行httprequest并显示下载响应的进度 https stackoverflow com questions 9594318 how to asynchronous perform a httpreq
  • Android 中的 Sugar ORM:更新 SQLite 中保存的对象

    我是在 Android 上使用 SQLite 和 Sugar ORM 进行应用程序开发的新手 并尝试阅读 Sugar ORM 文档 但没有找到有关如何更新 SQLite 中保存的对象的任何信息 更改对象属性后还可以保存对象吗 就像是 Cus
  • 如何在 Android TextView 中使用土耳其语字符,如“ş ç ı ö”?

    我想在 android TextView 中写入 ile 但它没有正确绘制 怎样才能使用这样的字符呢 例如 我将文本视图设置为 ile 它显示为 ile 我怎样才能解决这个问题 尝试以下方法 看看是否有帮助 source http grou
  • 如何访问android库项目中的资源

    我正在构建一个 android 库项目 它内部需要一些静态资源 图像 xml 等 然后我想知道我可以把这些资源放在哪里以及如何访问它们 既然我把资源放到了assets文件夹 我使用 AssetManager 来访问资源 public cla
  • Google 地图删除标记路线上下文菜单

    我使用 Android Studio 的 Google 地图模板启动了一个新项目 并在地图上添加了一个标记 LatLng location new LatLng lat lng Marker marker mMap addMarker ne
  • 如何在 NumberPicker 中一次显示 3 个以上的值

    我正在创建一个数字选择器 如下图所示 但如果有可用空间 我想显示 3 个以上的值 该选择器有 20 个项目 并且有足够的空间来显示 3 个以上的值 这可以使用 NumberPicker 来完成吗 只需以编程方式设置numberPicker
  • 使用 Android Studio 进行调试永远停留在“等待调试器”状态

    UPDATE The supposed重复是一个关于陷入 等待调试器 执行时Run 而这个问题就陷入了 等待调试器 执行时Debug 产生问题的步骤不同 解决方案也不同 每当我尝试使用Android Studio的调试功能时 运行状态总是停
  • 来自相机的 MediaCodec 视频流方向和颜色错误

    我正在尝试流式传输视频捕获直接从相机适用于 Android 设备 到目前为止 我已经能够从 Android 相机捕获每一帧预览帧 byte data Camera camera 函数 对数据进行编码 然后成功解码数据并显示到表面 我用的是安
  • 如何从android中的外部存储中获取所选文件的文件路径?

    我在选择文件的文件路径时遇到问题 我搜索了整个堆栈溢出 但问题没有解决 从设备中选择文件的代码如下所示 Intent intent new Intent Intent ACTION GET CONTENT intent setType in
  • Android 纹理仅显示纯色

    我正在尝试在四边形上显示单个纹理 我有一个可用的 VertexObject 它可以很好地绘制一个正方形 或任何几何对象 现在我尝试扩展它来处理纹理 但纹理不起作用 我只看到一种纯色的四边形 坐标数据位于 arrayList 中 the ve
  • 如何使用应用程序接口将蓝牙套接字传递给另一个活动

    因此 根据我收集的信息 套接字连接既不可序列化 也不可分割 但我需要将蓝牙连接传递给另一个活动 我不想作为中间人编写服务 所以请不要将此作为解决方案发布 我听说有一种方法可以使用自定义应用程序接口来传递这些类型的对象 但我一生都找不到这样的
  • 未解决的包含:“cocos2d.h” - Cocos2dx

    当我在 Eclipse 中导入 cocos2dx android 项目时 我的头文件上收到此警告 Unresolved inclusion cocos2d h 为什么是这样 它实际上困扰着我 该项目可以正确编译并运行 但我希望这种情况消失
  • SDK >=26 仍需要 mipmap/ic_launcher.png?

    在 Android 中 有两种指定启动器图标 可以说是应用程序图标 的方法 老 方式 在 mipmap 文件夹中指定不同的 png 文件 通常命名为 ic launcher png 但可以通过以下方式设置名称android icon mip
  • Glass 语音命令给定列表中最接近的匹配项

    使用 Glass 您可以通过 确定 Glass 菜单启动应用程序 它似乎会选择最接近的匹配项 除非命令相距数英里 并且您可以明显看到命令列表 无论如何 是否可以从应用程序内或从语音提示 在初始应用程序触发后 给出类似的列表并返回最接近的匹配
  • 是否可以使用 CardView 为浮动操作按钮制作阴影?

    I know CardView不是为此而设计的 但理论上如果cardCornerRadius view size 2它应该导致圆圈 我错过了什么吗 绘制真实的动画阴影并不困难 您可以尝试在 Froyo 等任何 Android 设备上实现 L
  • 如何构建自定义摄像机应用程序?

    我正在尝试开发一个自定义摄像机录像机 当我的设备在 Activity 的 beginRecording 中执行 start MediaRecorder 方法时 应用程序崩溃 我不知道出了什么问题 因为我遵循谷歌API指南 http deve
  • 在android中创建SQLite数据库

    我想在我的应用程序中创建一个 SQLite 数据库 其中包含三个表 我将向表中添加数据并稍后使用它们 但我喜欢保留数据库 就好像第一次安装应用程序时它会检查数据库是否存在 如果存在则更新它 否则如果不存在则创建一个新数据库 此外 我正在制作
  • Fragment 生命周期和在不存在的 Fragment 上调用 onCreate 的问题

    我正在 Android 中测试片段 并且片段生命周期有一些令人困惑的行为 我有一个活动 在横向和纵向模式下使用 xml 布局 我有一些代码可以访问在片段布局之一中定义的 EditText 对象 如果我以横向模式启动应用程序 一切都会正常 我
  • 如何正确编写AttributeSet的XML?

    我想创建一个面板适用于 Android 平台的其他小部件 http code google com p android misc widgets 在运行时 XmlPullParser parser getResources getXml R
  • 如何在布局编辑器中模拟沉浸式模式

    我想在布局编辑器中全屏查看我的布局 我正在使用 eclipse 插件 我已经通过选择隐藏了 ActionBar NoActionBar组合中的主题 但导航栏是一个不同的故事 AFAIK 它只能使用代码中的标志来隐藏 我需要在活动 xml 文

随机推荐

  • windows和linux共用蓝牙鼠标,Ubuntu和Windows双系统蓝牙设备共享配对

    蓝牙设备如键盘 鼠标都可以 装的双系统win7和Ubuntu xff0c 如果只使用一个系统 xff0c 蓝牙鼠标配对一次后可以正常使用 xff0c 但如果下次进的另一个系统必须要重新配对才能使用 所以这篇文章就是解决这个问题的 xff0c
  • python类的实例化

    在 Python 中 xff0c 类的实例化是通过在类名后面加上圆括号的方式来创建一个类的实例 例如 xff0c 如果有一个名为 34 MyClass 34 的类 xff0c 可以通过如下方式创建一个该类的实例 xff1a span cla
  • 如何提取abaqus的位移和其坐标

    坐标 每次在 Abaqus 中运行作业时 xff0c 都会在工作文件夹中创建一个 job name INP 文件 该文件包含节点编号和原始坐标 xff0c 如附图所示 INP 文件可以用任何文本编辑器 xff08 word notepad
  • C语言怎么打印出枚举类型

    在 C 语言中 xff0c 你可以使用 printf 函数来打印枚举类型 首先 xff0c 你需要在枚举类型定义的时候为每一个枚举值赋予一个字符串的别名 xff1a span class hljs built in enum span sp
  • Android android_id, deviceId, oaid的区别

    一 ANDROID ID 1 ANDROID ID是设备首次启动时由系统随机生成的一串64位的十六进制数字 2 获取方式 String ANDROID ID span class token operator 61 span Setting
  • Android设备唯一标识(AndroidID,OAID等 )

    一 ID 体系 xff1a 你只是一串代码 想要了解 OAID xff0c 我们首先需要明白 ID 体系 xff1a 想要追踪一个用户就必须先找到用户 xff0c 在这个过程中 xff0c 标识符 xff08 ID xff09 就像我们的另
  • 如何使用tkinter中的pack使按钮在主窗体正中间

    使用pack 方法可以将按钮放置在主窗体正中间 xff0c 可以使用以下代码 xff1a btn pack expand 61 True fill 61 39 both 39 xff0c 其中expand 61 True表示按钮可以填充整个
  • python实例化的几种表示方式

    Python 中实例化的几种表示方式包括 通过类名直接实例化 如果定义了一个类 34 Person 34 可以使用 34 person 61 Person 34 来实例化该类 通过 super 函数实例化 可以使用 super 函数来实例化
  • 查看git项目工程的地址

    1 xff0c cmd控制台输入 git remote v 命令 首先 xff0c 进入到项目工程目录 xff1a 其次 xff0c 从该目录地址中 xff0c 进入cmd控制台 xff1a 然后 xff0c 输入git remote v
  • 使用Android Studio打包生成Jar包的方法(亲测可用)

    首先我们来说下打成jar包的分类 xff1a 1 application应用打成jar包 2 内库打成jar包 不管是那种打包jar包都得变成内库 xff0c 所以当一种方法讲解了 打jar包之前得让你的项目成内库 xff1a 如何变成内库
  • Android引包出现Duplicate class com.xxx.xxx found in modules错误

    方法一 xff1a 改依赖库的版本 其实就是依赖的包版本不一样 xff0c 产生了冲突 xff0c 把所有依赖的库改成版本一致的就可以了 注意 xff1a 如果有多个library的引用 xff0c 并且各自引入了 so库文件 xff0c
  • Android实现视频播放的3种实现方式

    Android提供了常见的视频的编码 解码机制 使用Android自带的MediaPlayer MediaController等类可以很方便的实现视频播放的功能 支持的视频格式有MP4和3GP等 这些多媒体数据可以来自于Android应用的
  • java:单例模式的五种实现方式

    1 概述 java单例模式是一种常见的设计模式 单例模式有以下特点 xff1a 1 单例类只能有一个实例 xff1b 2 单例类必须自己创建自己的唯一实例 xff1b 3 单例类必须给所有其他对象提供这一实例 xff1b 2 优缺点 优点
  • 如何反编译pyc即pyinstaller出的exe

    有位大佬的代码 https span class token operator span span class token comment blog csdn net qfcy article details 113245876 改pyin
  • Ubuntu 22.04 为 Jellyfin 配置代理

    问题背景 最近捡了一个 1L 小主机 目前装上了 Ubuntu 22 04 打算把这个小东西当作家庭服务器用起来 玩这个当然少不了搭建一套私有影音系统啦 于是在网上找解决方案 自然而然地搭起了一套 Jellyfin 环境 然后就开始踩坑了
  • Undefined symbols for architecture arm64解决方案

    在iOS开发中经常遇到的一个错误是Undefined symbols for architecture arm64 xff0c 这个错误表示工程某些地方不支持arm64指令集 那我们应该怎么解决这个问题了 我们不仅要解决这个问题 更要了解出
  • AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher

    今天使用OkHttp的时候遇到一个坑 xff0c 加了句log后就报错 Log d TAG 34 onResponse 34 43 response body string String resp 61 response body stri
  • Android 关机广播和重启广播的监听

    关于如何在app应用中如何监听系统关机的信息 xff0c 并且做相应的逻辑处理 xff0c 比如存储某些数据等操作 我们都知道开机完成的时候有开机广播 xff0c 那么关机的时候有没有关机广播呢 xff1f 那么Android官方的回答是有
  • 稳定获取Android设备唯一码(UUID)的解决方案

    最近做的一个项目中需要用到Android设备唯一码 xff08 UUID xff09 来标识一台设备 xff0c Android中设备唯一码有很多 xff0c 如 xff1a MAC地址 IMEI号 xff08 DeviceId xff09
  • Android消息推送MQTT实战

    1 前言 年初做了一款Android TV 应用 xff0c 用到了MQTT 主要实现的是类似一些景区利用大屏幕实时显示景点人数 xff0c 超过人数就不允许进入 即利用闸机设备监控到进景区的游客 xff0c 然后通过MQTT将消息发送给大