移动基站信息APP—基于对象消息编程框架android开发的案例分析

本文主要是介绍移动基站信息APP—基于对象消息编程框架android开发的案例分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

       在我之前的文章《基于对象消息编程的android开发框架》中对我的对象消息编程框架及其在android方面的应用做了初步的介绍,其中也用两个案例做以简要说明,限于文章篇幅没有深入阐述,在此对上篇文章的移动基站信息案例进行较深入的分析,从而对消息编程进一步的了解。

案例app功能

     在通信公司运维中,需要知道移动基站的信号质量,根据信号质量而对基站进行调整或维护。为方便基站信息的搜集,因此我们设计一个手机端app使运维人员能方便的对基站信息采集。那么该app主要有以下功能:

1、首先通过手机获取周围基站的信息。如基站id、信号强度等。

2、根据基站id确定基站的物理位置。

3、将基站信息、基站位置进行本地数据库保存及上传到web服务器。

app将获取的信息传到服务器后,运维部门就可以根据服务器中的数据进行分析,判断移动信号服务地区盲点及基站服务情况。

程序主页面

页面元素:

1、按钮:

历史记录:打开记录页面。保存数据:将基站信息保存到本地数据库。上传数据:将数据上传到远端服务器。自动保存上传:开启自动保存、上传,间隔5秒执行一次。

2、文本标签

    分别用来显示手机获得的基站信息、从互联网获得的基站位置信息、上传结果、保存结果。

下面我们首先通过分析代码的流程来熟悉消息编程框架

主页面activity启动

  主页面对应activity代码为TelsignalActivityNew类。

public class TelsignalActivityNew extends TLBaseActivity implements View.OnClickListener{private  int nn=1;private  int mm=1;private  String cell_id="";private String lac ="";public TextView signaltext;TextView jizhantext;TextView postresulttext;TextView savetext;TLMsgBridge msgBridge;int i=1;int p=1;int m=1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_telsignal);//    msgBridge=getMsgBridge("saveReturn");signaltext = (TextView) findViewById(R.id.signaltext);jizhantext = (TextView) findViewById(R.id.jizhantext);postresulttext = (TextView) findViewById(R.id.postresulttext);savetext = (TextView) findViewById(R.id.savetext);addButton("button_m", R.id.button_recoders);addButton("button_post", R.id.button_post);addButton("button_autosave", R.id.button_autosave);addButton("button_save", R.id.button_save);if (ContextCompat.checkSelfPermission(TelsignalActivityNew.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(TelsignalActivityNew.this, new String[]{ Manifest.permission. ACCESS_COARSE_LOCATION }, 1);}setMyApp("TLSignalApp");putMyApp(new TLMsg("setup"));}@Overrideprotected void onStart(){super.onStart();}@Overridepublic void onClick(View v) {TLMsg msg =createMsg();switch (v.getId()) {case R.id.button_recoders:msg.setMsgId("startactivity");  ;msg.setParam("classname", RecoderActivity.class);putMsg(appCenter,msg);break;case R.id.button_post:msg.setAction("postData");putMyApp(msg);break;case R.id.button_save:msg.setAction("saveData");putMyApp(msg);break;case R.id.button_autosave:msg.clear();msg.setAction("autosaveData");putMyApp(msg);break;}}public TLMsg getMsg(Object fromWho, TLMsg msg) {switch (msg.getAction()) {case "getSignal":showResponse( msg) ;break;case "jizhan":showjizhan( msg) ;break;case "postReturn":postReturn( msg) ;break;case "serviceReturn":serviceReturn( msg) ;break;case "saveReturn":runOnUiThread(new Runnable() {@Overridepublic void run() {savetext.setText("保存成功"+i+"次!");i++;}});break;default:}return  null ;}private void serviceReturn(TLMsg msg) {final String content;if(msg.getParam("error")!=null && (boolean)msg.getParam("error")==false){content ="上传"+p+"次!"+ TLMsgUtils.msgToGson(msg);p++;}elseif(msg.getParam("netstate")!=null &&(boolean)msg.getParam("netstate")==false ){content="没有打开网络";}else{content="上传错误"+m+"次!";m++;}postresulttext.setText(content);}private void postReturn(TLMsg msg) {String status ="" ;if(msg.getParam("error")!=null && (boolean)msg.getParam("error")==false){        String response =(String) msg.getParam("response");//     response=(String)getBundleMsg().getParam("bundle") +mm+status+response;postresulttext.setText(response);}else{postresulttext.setText("上传错误"+p+"次!");p++;}}private void showjizhan(TLMsg msg) {String status ="" ;if(msg.getParam("error")!=null && (boolean)msg.getParam("error")==false){String response =(String) msg.getParam("response");jizhantext.setText(response);}elsejizhantext.setText("查询网络错误");}private void showResponse(TLMsg msg) {List<BaseDataBean> list =(List<BaseDataBean> )msg.getParam("jizhan") ;BaseDataBean data =list.get(0);cell_id =data.getCell_id();lac =data.getLac();signaltext.setText(nn+"  dbm:"+msg.getParam("dbm")+"  level:"+msg.getParam("signallevel")+"  cell_id:"+cell_id +"   lac:"+lac+"   lte_sinr:"+(int) msg.getParam("lte_sinr")+"   lte_rsrp:"+(int) msg.getParam("lte_rsrp")+"   lte_rsrq:"+(int) msg.getParam("lte_rsrq")+"   lte_rssnr:"+(int) msg.getParam("lte_rssnr")+"   lte_cqi:"+(int) msg.getParam("lte_cqi"));nn++;}@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {switch (requestCode) {case 1:if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {Toast.makeText(this, "请打开位置权限,拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();finish();}break;default:}}
}

在activity创建方法onCreate里,首先获取按钮及文本标签的实例,同时对按钮进行监听。为代码方便在父类自定义了一个addButton方法来实现按钮的实例获取及监听。

protected Button addButton(String name ,int id){Button button = (Button) findViewById(id);button.setOnClickListener((View.OnClickListener) this);buttons.put(name,button);return button;
}

初始化页面元素后,检查位置信息权限是否获取,如果没有申请权限。至此常规activity启动流程到此。

主页面启动后将自动显示出基站信息及通过互联网获取的基站位置信息,这是如何实现的呢。主要是下面两行代码:

setMyApp("TLSignalApp"); // 设置对应的业务模块
putMyApp(new TLMsg("setup")); //给业务模块发生启动消息

activity主要实现输入、输出显示功能,业务逻辑由对应的app模块TLSignalApp来实现。activity也不应该包含任何对象、组件的创建,这样activity是纯粹的view,与其他功能完全的分开。
这里通过setMyApp方法设置了对应的业务处理模块TLSignalApp,然后给模块发送了启动消息指令“setup”。细心同学发现,模块TLSignalApp是一个字符串名字,这里没有进行对象实例化,那这如何与具体的对象对应呢 ?这在程序主配置文件app_config.xml里面设置:
<module name="TLSignalApp" classfile=".demo.jizhan.TLSignalApp"></module>

这个配置的意思是模块名字为"TLSignalApp",指向对应的类文件".demo.jizhan.TLSignalApp"。框架将自动根据配置文件里面的模块名字由模块工厂创建模块实例。所有的模块、对象都有一个名字,通过名字来调用模块更加灵活,因为字符串比对象句柄更容易传递、保存,而且也实现一定的解耦和独立。如果想更换模块或类,只需更改配置文件对应的类文件即可。

由统一的模块工厂创建对象实例,方便实例的传递、共享。

TLSignalApp模块收到消息指令“setup”后,执行对应的消息指令,在TLSignalApp类代码中:

@Override
protected TLMsg myCheckMsgCmd(Object fromWho, TLMsg msg) {TLMsg returnMsg=null;switch (msg.getAction()) {case "setup":cell_id="";lac ="";putMsg(tltm,new TLMsg("init","listener",this).setParam("listenAction","getSignal"));break;

TLSignalApp通过方法myCheckMsgCmd获取传入的消息,分析指令,然后根据指令完成相应的功能。这里获取setup消息指令后开始启动基站信号采集。为实现信号采集,我设计了信号采集模块TLTelephonyManager类,采集模块在TLSignalApp初始化时进行了实例化,模块TLSignalApp也不负责创建其他对象,而是通过给模块工厂moduleFactory发送消息来获取模块实例,代码如下:

protected void init(){super.init();TLMsg msg =createMsg();//构建消息//设置消息指令及参数 msg.setAction("getModule").setParam("moduleName","cn.tianlong.tlandroid.utils.TLTelephonyManager")   ;//给工厂发送消息获取模块实例tltm= (TLTelephonyManager) putMsg(moduleFactory,msg).getParam("instance");

TLSignalApp在setup指令中,直接给信号采集模块实例tltm发送启动消息。启动消息通过setParam("listenAction","getSignal")告诉采集模块对于采集结果的返回消息指令是getSignal,采集模块实例tltm通过发送消息指令getSignal来传递采集信息。来看TLSignalApp模块中的返回消息指令代码:

case "getSignal":getSignal( msg);break;

TLSignalApp对于getSignal消息执行处理方法getSignal:

private void getSignal(TLMsg msg) {dbmsg=msg;List<BaseDataBean> list =(List<BaseDataBean> )msg.getParam("jizhan") ;data =list.get(0);dbmsg.setParam("cell_id",cell_id);if(cell_id.equals(data.getCell_id()) ==false|| lac.equals(data.getLac())==false){cell_id =data.getCell_id();lac =data.getLac();dbmsg.setParam("cell_id",cell_id);queryAddress();}msg.setAction("getSignal");putMsg((IObject) activity,msg);
}

在getSignal方法中,获得采集到的基站的信息,然后通过方法queryAddress()查询互联网获取基站位置信息,同时将基站信息发送给主activity页面显示:

msg.setAction("getSignal");
putMsg((IObject) activity,msg);

在主activity中,消息"getSignal"对应的处理方法为showResponse函数:

private void showResponse(TLMsg msg) {List<BaseDataBean> list =(List<BaseDataBean> )msg.getParam("jizhan") ;BaseDataBean data =list.get(0);cell_id =data.getCell_id();lac =data.getLac();signaltext.setText(nn+"  dbm:"+msg.getParam("dbm")+"  level:"+msg.getParam("signallevel")+"  cell_id:"+cell_id +"   lac:"+lac+"   lte_sinr:"+(int) msg.getParam("lte_sinr")+"   lte_rsrp:"+(int) msg.getParam("lte_rsrp")+"   lte_rsrq:"+(int) msg.getParam("lte_rsrq")+"   lte_rssnr:"+(int) msg.getParam("lte_rssnr")+"   lte_cqi:"+(int) msg.getParam("lte_cqi"));nn++;
}

showResponse方法通过设置文本框signaltext内容显示基站信息。

方法queryAddress()用于根据基站id查询基站位置信息:

 private void queryAddress(){TLMsg msg =createMsg();String url ="http://api.cellocation.com:81/cell/?";HashMap<String,String> params=new HashMap<>();params.put("mcc","460");params.put("mnc","1");params.put("lac",lac);params.put("ci",cell_id);params.put("output","xml");msg.setAction("get")    .setDestination(WEBSERVER).setParam("url",url).setParam("params",params).setParam("activity",activity).setParam("resultFor",this).setParam(RESULTACTION,"jizhanData");putMsg(appCenter,msg);}

方法的内部流程也基本是先创建消息,然后将消息发送给webserver。这里将消息发送给appCenter模块,通过在消息中指明当前消息目的:.setDestination(WEBSERVER),由appCenter路由转发给WEBSERVER,当然也可以直接将消息发送给WEBSERVER。对于WEBSERVER的返回消息,通过设置消息参数来告诉WEBSERVER结果返回给谁及返回的消息指令:

setParam("resultFor",this) // 结果返回给当前对象
 setParam(RESULTACTION,"jizhanData");// 返回的消息指令

在TLSignalApp的消息指令jizhanData中处理中获取到WEBSERVER的返回结果:

private void jizhandata(Object fromWho, TLMsg msg) {msg.setAction("jizhan");putMsg((IObject)activity,msg);
}

这里没有进一步的对web结果进行处理,而是直接发送给主activity显示。如果需要对web结果处理后再显示,可以在这加处理逻辑。在主activity代码中,针对发送来的web结果直接显示:

 private void showjizhan(TLMsg msg) {
        String status ="" ;
        if(msg.getParam("error")!=null && (boolean)msg.getParam("error")==false)
        {
            String response =(String) msg.getParam("response");
            jizhantext.setText(response);
        }
        else
            jizhantext.setText("查询网络错误");
    }

到此,在主activiy启动后,通过消息的传递,将模块建立、基站信息获取、基站地址获取、获取后的信息显示等各项任务自动完成,消息流程图如下:

从上图看activity启动后的消息流程:

1、activity 给 TLSignalApp发送启动消息

2、TLSignalApp给手机信号采集模块TLTelephonyManager发送启动采集消息

3、采集模块TLTelephonyManager讲采集结果送到TLSignalApp。

4、TLSignalApp将采集结果发送消息到activity,activity显示输出

5、TLSignalApp根据基站id发送消息给WEBSERVER查询互联网基站位置

6、WEBSERVER将查询到的基站位置返回给TLSignalApp。

7、TLSignalApp将基站位置发送消息到activity,activity显示输出

上面消息流程线路自然体现了一个事物之间的逻辑关系。而我们常规的设计缺少事物的逻辑,比如我们常见的一个设计形式:

 object  汽车1 =new 汽车();

汽车1.启动();

 汽车1.行驶();

 汽车1.停止();

这里面为什么先执行启动(),而不是行驶()? 代码不懂为什么,逻辑关系在哪呢?在开发者的头脑里。因此我们看其他人的代码就非常困难,我们要去猜设计者的思想逻辑。

查看历史记录功能

主activity另启动一个activity显示出保存在本地数据库的基站信息:

msg.setMsgId("startactivity");  ;
msg.setParam("classname", RecoderActivity.class);
putMsg(appCenter,msg);

前面我们说过activity只负责输入与输出功能,启动另一个activity的责任交由appCenter模块来执行。在appCenter模块中将"startactivity"消息传递给TLAndroid模块进行启动页面。TLAndroid模块相当于一个虚拟android环境,负责android个性组件的创建,这么设计主要是将模块功能分类,逻辑上比较清晰。在记录RecoderActivity页面里,创建一个recyclerView来显示保存的记录,其中所有的数据查询、显示也遵循上面的消息传递模式。

自动保存上传

已经有了手动保存、上传,自动模式无非是自动调用手动的方法,常规编程是启动两个线程,每个线程周期调用手动的方法,这里就涉及线程建立、传递对象实例及回调方法,这比较麻烦的。对象消息编程一切都是针对消息处理,因此在线程中只要将手动的消息执行即可,在TLSignalApp模块初始化init方法中:

        TLMsg tmsgsave=createMsg().setAction("saveData").setDestination(name).setParam("delay","5");TLMsg tmsgpost=createMsg().setAction("postData").setDestination(name).setParam("delay","5");jztask= (Jztask) getModule("jztask");putMsg(jztask,createMsg().setAction("registTask").setParam("msg",tmsgsave));putMsg(jztask,createMsg().setAction("registTask").setParam("msg",tmsgpost).setNextMsg(createMsg().setAction("setup")));

首先构建两个执行消息,这两个消息指明对应的手动数据保存、上传方法,然后获取任务模块jztask(该模块功能为周期运行消息),将执行消息注册到任务模块的任务表中,任务模块届时将自动运行消息。消息可以组成消息链,模块可对消息链中的消息依次执行,因此通过setNextMsg(createMsg().setAction("setup") 设置了下条消息指令为setup,这样任务模块注册完消息任务后自动执行setup消息,模块初始化。

理解了上面的消息传递过程分析,也很容易看懂数据保存、上传等功能的消息流程,这里不再叙述。

总结

      以往我们直接调用一个对象的方法,现在通过传递一个消息让对方执行,多了一个传递消息的过程,那么多一个环节的目的是什么呢?是不是多此一举?目的就是自己的事情自己做,不互相牵扯,保持独立和自由。当然具体实践时也要根据情况,简单的或底层的对象如果直接调用方法更有效率也不影响整体的灵活,那就直接调用,并没有绝对的规则。对于消息编程我认为一个可能弊端是运行效率,由过去执行一个方法变成执行多个方法肯定要多花时间。那么这要在具体实际中斟酌是否影响,因为效率存在多个环节,应用需求、设计方法、网络、服务器端、硬件等各方面。目前运行效率与开发效率是个矛盾,框架提高开发效率但必然降低程序运行效率。

      我认为任何的设计模式目的都是为了在达到应用需求前提下,尽量提高开发者工作效率。实现这目的关键是设计的灵活性和结构化。垂直上层次化,各层负责各层的事情、互不干涉;水平模块化,各模块自负其责、逻辑关系清晰,这样程序才具有灵活性也更方便维护。如果一个功能跨多层、分散到几个模块中,那么当然查错、更改就比较麻烦了。一个对象的对外开放方法越少,功能越专一,则其独立性越强,各模块相互影响就越小。在我的消息对象中只有两个公共方法——送出消息、接受消息,那么在这两个点检查消息就能清晰的划分责任,如果消息送来的是正确的,那么处理是自己的事情,如果消息送出是正确的,那么处理是对方的事情,责任很明确,这也更适于调试、合作开发。   

     想进一步了解对象消息框架请访问博客 https://blog.csdn.net/tianlong117。

 

    

这篇关于移动基站信息APP—基于对象消息编程框架android开发的案例分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/875380

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo