適用於 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 監聽即時事件並顯示於應用程式中