適用於 Raspberry Pi 的 Android 應用程式
在之前的物联网教程中,我们介绍了如何设置Raspberry Pi,以及如何使用 Back4AppAPI 将其连接到 Parse 服务器,从而将对象保存到服务器并执行查询和实时查询。
现在,我们将介绍如何将Raspberry上的所有操作复制到应用程序上。在本教程中,我们将介绍一个与之前配置的物联网设备进行交互的 Android 应用程序。在 Parse 服务器端,应用程序执行与Raspberry相同的任务:写入对象、执行查询和实时查询。请注意,即使你不打算开发物联网应用程序,这些功能也可能有用!
事实证明,Parse 是开发物联网应用的绝佳框架。2020 年,随着 GraphQL API 协议的加入,它将提供更好的数据检索方式。
我们提供的代码是您开发所需应用程序的第一步。
Contents
前提条件
唯一的先决条件是完成我们的 Android 快速入门教程。为此,您需要点击下面提到的链接。
https://www.back4app.com/docs/pages/android/how-to-build-an-android-app-on-back4app
下面是关于 LiveQuery 的教程,在本物联网系列教程的第 3 节中需要使用。不过您不必提前完成。
https://docs.back4app.com/docs/android/live-query/
如果你在整个教程中遇到任何问题,可以通过下面链接中的官方 Parse Android 指南来解决。
http://docs.parseplatform.org/android/guide/
第 1 节:创建应用程序和连接 Back4App 的基础知识
在本项目中,我们将类命名为 “MainActivity.java”。以下是基本代码,可以帮助您开始使用。
public class MainActivityextends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 在应用程序中插入工具栏。关于该工具栏的更多描述请参见 menu_main_activity.xml 文件
工具栏 toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
@Override
public boolean onCreateOptionsMenu(Menu 菜单) { // Inflate the menu; this is the function of the menu.
// 膨胀菜单;如果存在操作栏,则将项目添加到操作栏。
getMenuInflater().inflate(R.menu.menu_main_activity, menu);
返回 true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks.
// 在此处理操作栏项的点击。操作栏将
// 自动处理主页/向上按钮的点击,只要
// 只要您在 AndroidManifest.xml 中指定了父活动。
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
返回 true;
}
return super.onOptionsItemSelected(item); } }
您可能需要添加以下导入:
importandroid.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; 导入 android.support.v7.widget.Toolbar; import android.view.View import android.view.Menu; import android.view.MenuItem; import android.widget.EditText; import android.widget.Button; import android.widget.ToggleButton; 导入 android.view.View.OnClickListener;
在布局 XML 文件中,确保添加了以下代码:
<?xml version="1.0" encoding="utf-8"? <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/myCoordinatorLayout" tools:context="com.example.guilherme.myiotapplication.MainActivity">
<includelayout="@layout/content_main_activity" />
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent" </p
android:layout_height="wrap_content" /> <android.support.design.widget.AppBarLayout
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent" > <android.support.v7.widget.Toolbar
android:layout_height="wrap_content"
android:background="?
app:popupTheme="@style/AppTheme.PopupOverlay"/>应用程序主题
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout
</android.support.design.widget.CoordinatorLayout>
要将应用程序连接到 Back4App,请在类的 onCreate() 函数中添加以下代码。
// 初始化 Parse 服务器
Parse.initialize(new Parse.Configuration.Builder(this)
.applicationId("YOUR_APP_ID")//核心设置,"功能 "部分
.客户端密钥("YOUR_CLIENT_KEY")//来自核心设置,关于 "功能"
.server("https://parseapi.back4app.com/")
.build()
);
请记住,请按照 QuickStart 上的步骤说明为您的应用程序授予 Internet 访问权限。
第 2 部分:在 Parse 上保存和检索对象并在应用程序上显示
首先添加以下导入
importcom.parse.FindCallback; import com.parse.Parse.FindCallback import com.parse.ParseException; import com.parse.ParseObject; import com.parse.ParseQuery import com.parse.SaveCallback; import org.json.JSONException; import org.json.JSONObject; import java.util.List;
在本节中,我们将通过点击按钮保存 “CommandGPIO1 “类的对象。每个 Parse Object 都有三个默认属性:objectId、 createdAt 和updatedAt。我们的类还有两个附加属性:content和destination。content的值要么是 “on”(打开),要么是 “off”(关闭),代表要发送到 LED 的状态。对于目的地,所有对象都将包含 “command”,但我们可以为不同的 LED 定义不同的字符串。
举个例子:
我们创建了两个按钮,每个按钮都会在内容上写入不同的值。要创建按钮,请在布局 XML 文件中添加以下代码:
<按钮 android:id="@+id/buttonSendOn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="顶部|开始" android:layout_marginBottom="0dp" android:layout_marginTop="120dp" android:layout_marginEnd="0dp" android:layout_marginRight="0dp" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:text="Send ON"/>在类文件中创建按钮。
在类文件中,创建按钮就像写代码一样简单:
Button buttonOn = (Button) findViewById(R.id.buttonSendOn);
如果您是 Android 编程初学者,请注意 “findViewById “参数中包含 “buttonSendOn”,而这正是我们在 XML 中定义的Android:Id。
我们希望这个按钮在被点击时能在 Parse Server 上保存一个对象。为此,请添加以下代码:
buttonOn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(final View view) {
// 创建新对象并分配适当属性
ParseObject command =new ParseObject("CommandGPIO1");
command.put("content","on");
command.put("destination","command");
command.saveInBackground(new SaveCallback(){
@Override
public void done(ParseException e){
Snackbar.make(view,"Sent ON to Output", Snackbar.LENGTH_LONG )
.setAction("Action",null).show();
}
});
});
注意,我们可以在onClick 回调函数中添加任何我们想要执行的函数。在这里,我们创建了一个 “CommandGPIO1 “类对象,将内容设为 “on”,目标设为 “command”。
值得注意的是,我们不必事先在 Parse 面板上定义这个类!如果你后来选择添加新属性或更改类名,那么你只需修改代码,更改内容就会在仪表板上自动更新。
此时,你最好测试一下你的应用程序,检查对象是否正在创建!


如上图所示,SaveCallback 会在屏幕底部显示一个轻量级的反馈小条。
复制这段代码,创建一个写入 “关闭 “对象的按钮。更改布局 XML 中的以下几行
android:id="@+id/buttonSendOff" android:layout_gravity="top|center" android:layout_marginLeft="0dp" android:layout_marginStart="0dp" android:text="发送关闭"/>。
创建一个新按钮,并更改写入内容的行:
Button buttonOff = (Button) findViewById(R.id.buttonSendOff);
command.put("content","off");
现在 Parse 服务器已经正常工作了,我们希望向应用程序用户提供永久性反馈。为此,我们将使用 EditText 元素,如下所示。
在布局 XML 中定义该元素:
< 编辑文本
android:id="@+id/status1Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:maxLines="2"
android:ems="10"
android:singleLine="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
在类的 onCreate() 作用域中,添加以下代码:
// 定义文本元素,用于显示接收应用程序命令的输出引脚的状态
// 命令由按钮发送,稍后将在此代码中介绍。
final EditText textOutPin = (EditText) findViewById(R.id.status1Text);
textOutPin.setFocusable(false);
textOutPin.setClickable(true);
textOutPin.setText("Loading status of output pin...");
第一行创建了对象。第二行使用户无法编辑该对象。第三行使其可点击,以便复制和粘贴。第四行设置初始文本。
我们希望将 Parse 保存的最后一个 “CommandGPIO1 “对象的内容字段写入 EditText 对象。虽然我们可以通过代码中的一些逻辑来实现这一目的,但实际上我们将通过执行 ParseQuery 从 Parse 中获取对象,因为这样可以获得更真实、更强大的结果。
下面的代码声明、设置参数并打印 Parse 查询的结果。
ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
queryOut.whereEqualTo("destination","command");
queryOut.addDescendingOrder("createdAt");
queryOut.setLimit(1);
queryOut.findInBackground(new FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> objects, ParseException e) {
if (e ==null){
textOutPin.setText("Output is " + objects.get(0).getString("content"));
}
else{
textOutPin.setText("Error:" + e.getMessage());
}
}
});
第一行创建查询。第二行添加了一个约束,只选择目标字段包含 “command “的对象。第三行从最新的对象开始。第四行将结果限制为一个,确保我们将检索到最新的对象。
第五行激活查询,并在查询完成后调用回调函数。在这里,我们将检索到的对象内容写入先前定义的 EditText 对象。
我们将在声明 EditText 对象后立即添加这段代码,以便在应用程序打开时执行查询,以及在 Parse 上保存新对象时在SaveCallback中执行查询,从而在创建新对象时自动更新文本。
至此,您的应用程序应该可以像下面的屏幕截图所示那样运行。


最后,我们添加一个刷新按钮,让用户可以随时执行上述查询。我们将使用不同风格的按钮,即浮动操作按钮。
在布局 XML 文件中添加以下代码
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_marginBottom="0dp"
android:layout_marginTop="120dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginLeft="0dp"
android:layout_marginStart="0dp"
app:srcCompat="@android:drawable/ic_popup_sync" />
现在,在类的 onCreate() 作用域中添加以下代码:
// 刷新按钮可在请求时获取输出状态。
// 在此执行与第一次相同的查询
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(final View view){
ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
queryOut.whereEqualTo("destination","command");
queryOut.addDescendingOrder("createdAt");
queryOut.setLimit(1);
queryOut.findInBackground(new FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> objects, ParseException e) {
if (e ==null){
textOutPin.setText("Output is " + objects.get(0).getString("content"));
}
else{
textOutPin.setText("Error:" + e.getMessage());
}
Snackbar.make(view,"Updated status of Output", Snackbar.LENGTH_LONG ).setAction("Action",null).show();
}
});
}
});
至此,我们就可以在 Parse 服务器上保存对象、从中获取信息并在应用程序中显示它们了!你的应用程序应该可以如下图所示运行:

第 3 节:使用实时查询监听实时事件并在应用程序中显示
在本节中,我们将实时监控 Parse Dashboard 中的 “InputGPIO “类,并为用户显示对象的 “内容”。
首先在布局 XML 中定义一个新的 EditText:
< 编辑文本
android:id="@+id/status2Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|start"
android:layout_marginBottom="0dp"
android:maxLines="2"
android:ems="10"
android:singleLine="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
在 onCreate() 的作用域中,创建一个新的 EditText 对象,然后立即执行初始化查询(尚未启用)。
final EditText textInPin = (EditText) findViewById(R.id.status2Text);
textInPin.setFocusable(false);
textInPin.setClickable(true);
textInPin.setText("Loading status of input pin...");
// 初始(非实时)查询,获取上次存储的引脚状态
ParseQuery<ParseObject> queryIn = ParseQuery.getQuery("InputGPIO");
queryIn.whereEqualTo("type","interrupt");
queryIn.addDescendingOrder("createdAt");
queryIn.setLimit(1);
queryIn.findInBackground(new FindCallback<ParseObject>() {
@Override
public void done(List<ParseObject> objects, ParseException e) {
if (e ==null){
textInPin.setText("Input is " + objects.get(0).getString("content"));
}
else{
textInPin.setText("Error:" + e.getMessage());
}
}
});
请注意,”InputGPIO “的属性是内容和类型,后者在 “CommandGPIO “类中扮演目的地的角色。
至此,您的应用程序应该如下图所示。

现在,我们将实现实时查询。这里需要参考前面提到的教程。
https://docs.back4app.com/docs/android/live-query/
请务必按照 Live Query 教程中的第 1 步操作,选择在哪些类中启用 Live Query 并定义子域名称。您可能需要创建 “InputGPIO “类(如果还不存在),以便在该类上启用该功能。按照教程中的第 2 步在 Android 上设置 Live Query 客户端。
记住添加以下导入:
importtgio.parselivequery.BaseQuery; 导入 tgio.parselivequery.LiveQueryClient; 导入 tgio.parselivequery.LiveQueryEvent; 导入 tgio.parselivequery.Subscription; 导入 tgio.parselivequery.interfaces.OnListener;
添加以下代码初始化 LiveQuery 并定义其参数。
// 初始化实时查询
LiveQueryClient.init("wss:YOUR_SUBDOMAIN_NAME.back4app.io","YOUR_APP_ID",true );
LiveQueryClient.connect();
// 定义 LiveQuery 的属性
Subscription subIn =new BaseQuery.Builder("InputGPIO")
.where("type","interrupt")
.addField("content")
.build()
.subscribe();
在定义订阅时,我们只选取type属性为 “interrupt “的元素,并检索字段内容。
现在,添加以下代码,以便在 Parse 服务器创建订阅所定义的对象时做出响应。
// 开始监听 LiveQuery CREATE 事件,获取其内容并编写
subIn.on(LiveQueryEvent.CREATE,new OnListener() {
@Override
public void on(JSONObject object) {
try {
final String subInContent = (String) ((JSONObject) object.get("object")).get("content");
runOnUiThread(new Runnable() {
@Override
public void run() {
textInPin.setText("Input is " + subInContent);
Snackbar.make(findViewById(R.id.myCoordinatorLayout),"Input pin was changed to" + subInContent.toUpperCase(), Snackbar.LENGTH_LONG ).setAction("Action",null).show();
}
});
}catch (JSONException e){
e.printStackTrace();
}
}
});
对象的内容字段将显示在新的 EditText 元素中。
至此,我们所做的工作足以显示物联网设备发送的任何输入。不过,为了验证应用程序的运行情况,我们将实现另一种按钮,即切换按钮(Toggle Button),它将创建一个 “InputGPIO “类对象并将其保存在 Parse Server 上。
在布局 XML 文件中添加以下代码:
<ToggleButton
android:id="@+id/toggleTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|end"
android:layout_marginBottom="0dp"
android:layout_marginTop="100dp"
android:layout_marginEnd="0dp"
android:layout_marginRight="0dp"
android:layout_marginLeft="0dp"
android:layout_marginStart="0dp"
android:textOn = "打开输入"
android:textOff = "输入关闭"/>。
在 MainActivity 类的 onCreate() 函数的作用域中添加以下代码。
// 这里的切换按钮只是为了模拟硬件创建的对象
ToggleButton toggleTest = (ToggleButton) findViewById(R.id.toggleTest);
toggleTest.setOnClickListener(new OnClickListener(){
@Override
public void onClick(final View view) {
ParseObject command =new ParseObject("InputGPIO");
if(gpioStatusTest.equals("off"))
gpioStatusTest ="on";
否则
gpioStatusTest ="关闭";
command.put("type","interrupt");
command.put("content","From Toggle: " +gpioStatusTest);
command.saveInBackground(new SaveCallback(){
@Override
public void done(ParseException e){
Snackbar.make(view,"Changed input pin", Snackbar.LENGTH_LONG ).setAction("Action",null).show();
}
});
}
});
另外,在 MainActivity 作用域中声明字符串。
StringgpioStatusTest ="off";
应用程序就完成了!本教程结束时,您的应用程序应该与下图非常相似。

如果您想继续开发我们的物联网应用程序,请阅读我们的物联网系列,这是一个循序渐进的指南,教您设置 Raspberry Pi 的基础知识和具体的应用程序,例如使用 JavaScript 从 Parse 服务器检索和保存对象。
我们的代码可从以下链接获取:
https://github.com/back4app/iot-raspberry-node
参考资料
如何讓 Android 應用與物聯網裝置互動?
第1部分:建立應用程式並連接伺服器端的基本知識
第2部分:在 Parse 上儲存與擷取物件,並顯示於應用程式中
第3部分:使用 Live Query 監聽即時事件並顯示於應用程式中


