话不多说,直接开干!
先来看看效果
本APP是通过tcp/ip协议与连了WiFi的单片机通信。
第一步:
你得会一点点java基础,你可以去百度搜“java菜鸟教程”,学了基本的基础即可,不必太深入。
第二步:
建议看一本书,是郭霖大神些的《第一行代码》,入门安卓基础。不想看书就忽略吧,我建议还是看一下最基本的例如:安卓四大组件----活动。
如果你真的一点都不想学。那就先跳过这一步。
第三步:安装Android studio软件。不会安装的小白,看过来!
注意:要安装Android studio,前提一定要安装了jdk环境。
jdk环境怎么安装?如果你电脑已经安装过eclipse并有使用过的话,就不用再安装jdk了。
打开这个链接:https://mp.weixin.qq.com/s/HKdtcd11Zk3HV3fh8_dtcw,跟着步骤做,做到15步即可。当然,如果你想要顺便把eclipse也给安装了,也无所谓。
装完jdk之后,就可以开始装Android studio了。
打开这个链接:https://www.bilibili.com/read/cv8151954
然后跟着步骤做吧。对了,下载的安装包,我建议选3.5.2 的!!!特别是小白,不然我的代码到时候你导进去各种错误可别来抱怨。
都安装完了就继续往前吧!
第四步:新建项目,开始撸代码
默认点击我标记的红色框框。请跟着我的图片操作哦。
我们给自己的项目起个名字哈:SmartHome
第二个小框框是包名,我们先不用理它是干什么的
第三个小框框是项目的存储路径
其他的不解析了
好啦我们直接开始敲代码啦!
我们先来写界面吧!
然后把我的代码复制粘贴
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<LinearLayout
android:layout_marginTop="20dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:layout_marginLeft="10dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:id="@+id/IPText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:textSize="20dp"
android:hint="请输入衣柜服务端的IP地址" />
<Button
android:layout_marginRight="10dp"
android:layout_weight="2"
android:id="@+id/StartConnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="开始连接" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/tv1"
android:layout_marginLeft="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="通风"
android:textSize="25sp"
android:id="@+id/textView" />
<Switch
android:id="@+id/switch_c"
android:layout_marginLeft="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="OFF"
android:textOn="ON"
android:thumb="@drawable/thumb"
android:track="@drawable/track"
/>
</LinearLayout>
<LinearLayout
android:gravity="center"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_marginLeft="10dp"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="抽湿"
android:textSize="25sp"
android:id="@+id/textView2" />
<Switch
android:id="@+id/switch_t"
android:layout_marginLeft="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="OFF"
android:textOn="ON"
android:thumb="@drawable/thumb"
android:track="@drawable/track"
/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_marginLeft="20dp"
android:layout_marginTop="15dp"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="温湿度:0℃"
android:id="@+id/temp_text" />
<TextView
android:layout_marginLeft="20dp"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="气体浓度:0%"
android:id="@+id/mq_text" />
</LinearLayout>
<TextView
android:id="@+id/test"
android:layout_gravity="center"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文字显示"/>
<Button
android:id="@+id/button_test"
android:text="点击我就会变成别的文字"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</RelativeLayout>
填写名字
thumb.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_checked="true" android:drawable="@drawable/green_thumb" />
<item android:drawable="@drawable/gray_thumb" />
</selector>
是不是又报错啦?如下图?
以刚才建drawable资源文件的方式,建以下5个drawable资源文件。
1.gray_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<size android:height="40dp" android:width="40dp"/>
<corners android:radius="20dp"/>
<gradient
android:endColor="#ffffff"
android:startColor="#ffffff" />
<stroke android:width="1dp"
android:color="#9e9e9e"/>
</shape>
2.gray_track.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<size android:height="40dp" android:width="40dp"/>
<corners android:radius="20dp"/>
<gradient
android:endColor="#ffffff"
android:startColor="#ffffff" />
<stroke android:width="1dp"
android:color="#9e9e9e"/>
</shape>
3.green_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<size android:height="40dp" android:width="40dp"/>
<corners android:radius="20dp"/>
<gradient
android:endColor="#ffffff"
android:startColor="#ffffff" />
<stroke android:width="1dp"
android:color="#9e9e9e"/>
</shape>
4.green_track.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<size android:height="40dp" android:width="40dp"/>
<corners android:radius="20dp"/>
<gradient
android:endColor="#ffffff"
android:startColor="#ffffff" />
<stroke android:width="1dp"
android:color="#9e9e9e"/>
</shape>
5.track.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_checked="true" android:drawable="@drawable/green_track" />
<item android:drawable="@drawable/gray_track" />
</selector>
以上是比较重要的UI控件,大家了解一下,其他的一些属性我就不多说了,你们可以自己尝试修改一下参数,就会发现具体的对应的作用是什么了。
我们的ui已经做好了,现在就去写主活动类的逻辑代码!!!
我主要说说主要逻辑吧。
首先,当然是要把我们前面编写的界面都映射到我们的主活动, setContentView(R.layout.activity_main);//该activity映射的xml界面是activity_main.xml
,就是这一句,然后,开启严苛模式,我们使用StrictMode,系统检测出主线程违例的情况并做出相应的反应,最终帮助开发者优化和改善代码逻辑。
接下来就是,初始化view组件 ` initView();//初始化显示的功能控件
最后对某些组件设置监听事件,以及编写对应的函数
重点来了
我们想要APP和单片机进行tcp通信,要怎么实现呢?
建立socket连接实现tcp/ip协议----这个就是我们的实现方式。
那应该怎么做?
1.创建Socket(安卓作为客户端,所以是client,单片机作为server端)。
2.打开连接到Socket的输入/输出流。
3.按照协议对Socket进行读/写操作。
4.关闭输入输出流、关闭Socket。
也许说了你们可能还是不太懂,这个用代码怎么实现啊?
我先说明一下,java socket的实现和安卓平台socket的实现有一点点的区别,*在安卓里面,涉及到网络连接等耗时操作时,不能将其放在UI主线程中,需要添加子线程,在子线程进行网络连接,这就涉及到安卓线程间的通信了,用Handle来实现。这里的子线程也就是 mThreadClient。
下面直接看我的代码----MainActivity.java
package com.example.smarthome;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
public class MainActivity extends AppCompatActivity {
private Button startButton;
private EditText IPText;
private boolean isConnecting=false;
private Thread mThreadClient=null;
private Socket mSocketClient=null;
private static PrintWriter mPrintWriterClient=null;
private String res="";
private TextView warning_show, temp, mq;
private String []send_order={"1\n","2\n","3\n","4\n"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
strictMode();
initView();
}
private void strictMode(){
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
);
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
private void initView(){
warning_show = findViewById(R.id.tv1);
temp = findViewById(R.id.temp_text);
mq = findViewById(R.id.mq_text);
IPText= findViewById(R.id.IPText);
IPText.setText("192.168.1.127:8080");
startButton= findViewById(R.id.StartConnect);
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(isConnecting)
{
isConnecting=false;
if(mSocketClient!=null)
{
try{
mSocketClient.close();
mSocketClient = null;
if (mPrintWriterClient!=null){
mPrintWriterClient.close();
mPrintWriterClient = null;
}
mThreadClient.interrupt();
startButton.setText("开始连接");
IPText.setEnabled(true);
warning_show.setText("断开连接\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}else
{
mThreadClient = new Thread(mRunnable);
mThreadClient.start();
}
}
});
final Switch switch_c=findViewById(R.id.switch_c);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch_c.setShowText(true);
}
switch_c.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked)
{
switch_c.setSwitchTextAppearance(MainActivity.this,R.style.s_true);
switch_c.setShowText(true);
if (send(send_order[0],-1)){
showDialog("开启通风");
}else{
switch_c.setChecked(false);
}
}else{
switch_c.setSwitchTextAppearance(MainActivity.this,R.style.s_false);
switch_c.setShowText(true);
if (send(send_order[1],-1)){
showDialog("关闭通风");
}else{
switch_c.setChecked(false);
}
}
}
});
final Switch switch_t=findViewById(R.id.switch_t);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch_t.setShowText(true);
}
switch_t.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked)
{
switch_t.setSwitchTextAppearance(MainActivity.this,R.style.s_true);
switch_t.setShowText(true);
if (send(send_order[2],-1)){
showDialog("开启抽湿");
}else{
switch_t.setChecked(false);
}
}else{
switch_t.setSwitchTextAppearance(MainActivity.this,R.style.s_false);
switch_t.setShowText(true);
if (send(send_order[3],-1)){
showDialog("关闭抽湿");
}else{
switch_t.setChecked(false);
}
}
}
});
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
String msgText = IPText.getText().toString();
if(msgText.length()<=0)
{
Message msg = new Message();
msg.what = 5;
mHandler.sendMessage(msg);
return;
}
int start = msgText.indexOf(":");
if((start==-1)||(start+1>=msgText.length()))
{
Message msg = new Message();
msg.what = 6;
mHandler.sendMessage(msg);
return;
}
String sIP= msgText.substring(0,start);
String sPort = msgText.substring(start+1);
int port = Integer.parseInt(sPort);
BufferedReader mBufferedReaderClient;
try
{
mSocketClient = new Socket();
SocketAddress socAddress = new InetSocketAddress(sIP, port);
mSocketClient.connect(socAddress, 2000);
mBufferedReaderClient =new BufferedReader(new InputStreamReader(mSocketClient.getInputStream()));
mPrintWriterClient=new PrintWriter(mSocketClient.getOutputStream(),true);
Message msg = new Message();
msg.what = 1;
mHandler.sendMessage(msg);
}catch (Exception e) {
Message msg = new Message();
msg.what = 2;
mHandler.sendMessage(msg);
return;
}
char[] buffer = new char[256];
int count ;
while(true)
{
try
{
if((count = mBufferedReaderClient.read(buffer))>0)
{
res = getInfoBuff(buffer,count)+"\n";
Message msg = new Message();
msg.what = 4;
mHandler.sendMessage(msg);
}
}catch (Exception e) {
Message msg = new Message();
msg.what = 3;
mHandler.sendMessage(msg);
}
}
}
};
@SuppressLint("HandlerLeak")
Handler mHandler = new Handler()
{
@SuppressLint("SetTextI18n")
public void handleMessage(Message msg)
{
super.handleMessage(msg);
if(msg.what==4)
{
char []arrs;
arrs=res.toCharArray();
if (arrs.length>=3) {
if (arrs[0]=='T'){
temp.setText("温湿度:"+arrs[1]+arrs[2]+"℃");
}
else if (arrs[0]=='M'){
mq.setText("气体浓度:"+arrs[1]+arrs[2]+"%");
}
}else {
showDialog("收到格式错误的数据:"+res);
}
}else if (msg.what==2){
showDialog("连接失败,服务器走丢了");
startButton.setText("开始连接");
}else if (msg.what==1){
showDialog("连接成功!");
warning_show.setText("已连接智能衣柜\n");
IPText.setEnabled(false);
isConnecting = true;
startButton.setText("停止连接");
}else if (msg.what==3){
warning_show.setText("已断开连接\n");
}else if (msg.what==5){
warning_show.setText("IP和端口号不能为空\n");
}
else if (msg.what==6){
warning_show.setText("IP地址不合法\n");
}
}
};
private void showDialog(String msg) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setTitle(msg);
builder.setCancelable(false);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create().show();
}
private String getInfoBuff(char[] buff,int count)
{
char[] temp = new char[count];
System.arraycopy(buff, 0, temp, 0, count);
return new String(temp);
}
private boolean send(String msg,int position){
if(isConnecting&&mSocketClient!=null){
if ((position==-1)){
try
{
mPrintWriterClient.print(msg);
mPrintWriterClient.flush();
return true;
}catch (Exception e) {
Toast.makeText(MainActivity.this, "发送异常"+e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}else{
showDialog("您还没有连接衣柜呢!");
}
return false;
}
}
这个时候,我们离成功就更近一步了。包名要和自己的包名一致啊。
接下来,看图
<uses-permission android:name="android.permission.INTERNET" />
一定要加上这句话哦,不然的话,就联网不成功了。
都加上了吗???
style.xml 这个忘了给,现在补上,在res的value的文件夹里,不然会有部分代码报错哦
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="s_true" parent="@android:style/TextAppearance.Small">
<item name="android:textColor">#33da33</item>
</style>
<style name="s_false" parent="@android:style/TextAppearance.Small">
<item name="android:textColor">#ffffff</item>
</style>
</resources>
我们要准备测试啦!
注意啦,如果你的电脑是运行8G以上的,就可以用模拟器运行,如果是4G的,我建议用真机测试。
4G的看过来,教你怎么快速把APP安装到手机,不用USB连接。
这个就是跳出来的文件夹
安装好了吗?我们再等等8G的朋友~
8G的朋友们还在吗?来了来了
你们一打开是这样的
然后就弄好模拟器啦
我的模拟器在开机,要等一等,等它开机完再运行。
这个是开机完的状态
要等它安装,我的安装好啦
测试了
怎么测试?直接拿单片机测试吗?
不是哦,我们可以通过网络调试助手进行测试。
需要的点网络调试助手
下载之后
然后确认你电脑的ip地址是什么?
win+R
ipv4地址就是你本机的地址。要和APP填写的一致。
测试开关按钮
测试数据接收
终于都弄好了!!!
累了累了…
后面要是自己加功能或者改功能怎么做?
我就随便讲几个例子,以后大家根据这些例子举一反三吧。
例1:如果我想增加一个文字显示,应该怎么做?
跟着我一起来动动手吧
这时候问题又来了,如果我想让它动态显示呢?就是当我点击了按钮它就换成别的文字?
这我们就得在主活动类对它进行绑定了,怎么绑定?
如果我们想要在成功连接了单片机之后,显示这个文字内容“已连接单片机”,我们就可以在对应的代码里增加代码textView.setText("已连接单片机");
然后我们运行一下APP
好啦,文字显示的就讲到这里了。
例2:如果我想增加一个按钮控件呢
它和文字显示的代码有什么区别呢?基本上是没有啥区别的,用法都差不多。
<Button
android:id="@+id/button_test"
android:text="按钮"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
只不过如果是按钮要多了个监听点击事件。绑定id的方式也是TextView差不多的哦。
那么。怎么给这个按钮添加监听事件呢?
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
button.setText("真的变了耶");
}
});
现在运行一下APP看看
当然,我不是要你的按钮做这件事,你可以在监听事件的代码加上任何你想做的事。
下一篇出kotlin版本的哦。
也许有小伙伴开始学的不是java版本的安卓,所以,主活动类可能代码看的会很吃力。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)