我用是友善之臂的NanoPC-T4开发板,CPU是瑞芯微rk3399,搭载android8.1版本,之后的实验都基于此平台。
开发板的wiki百科在http://wiki.friendlyarm.com/wiki/index.php/NanoPC-T4/zh,有兴趣的可以阅读一下。我不是打广告,我也是二手市场淘的,别人不要我便宜买了。
这个Hellworld的最终功能是操控一个gpio管脚,控制外接led灯的亮和灭。
理论上分三步:
1,实现一个按钮控件
2,利用JNI在控件的点击事件回调中调用so(linux的动态链接库)的接口
3,实现gpio的linux驱动(内核态),并在上述的so(用户态)中暴露出操作接口。
现在先实现一个按钮控件。
下面比较详细记录项目创建过程,因为我自己也是新手,算是自用的笔记。
创建一个空的窗口:
指定项目名称和目录,我这里的源码路径是com.mkelehk.helloworld,其实后面可以修改的,譬如将mkelehk改为demo文件夹:
按下“Finish”后,AS就自动为我们创建了一些源文件,譬如布局xml文件和java文件。我们可以通过修改xml文件来完成控件的布局,也可以拖动控件到“Design”视图里完成布局。右上角有个code-split-Design的选择,选择code的话只看xml文件,选择Design只看效果图,下图选择split:
我们可以用“Project”的模式来看工程文件,默认是以“Android”模式来展示源文件,在Project->app->res->layout->activity_main.xml(红框)就是布局xml文件了。默认有个“Hello world”的文本框。我们可以修改它,删掉一些位置信息,让文本框放在最上面:
其中wrap_content是根据内容(文本)来适配宽高。修改文字为:“Used to control led lights!”,用来提示该app的功能。
我们继续添加一个“按钮”,我们不采用拖控件的方式,而是直接修改xml,我发现AS在自动补全方面做得很好,譬如,输入“<”后就马上有各种控件的提示:
我们选择“Button”即可。
选择完Button之后,AS又立马自动补全其他必须的一些字段,譬如,宽高等:
按钮的宽我们选择“match_parent”,其中parent是外面的view框,也就是宽度扩展为跟view宽度一样:
但这样又覆盖掉之前弄好的文本框了,我们可以使用LinearLayout来进行布局,同时采用垂直方式分布:
这里有个问题,为什么我会知道orientation是用于垂直分布的呢,其实我们可以点击右上角的“Design”查看视图,右边栏有一列属性,可以供参考,英语好的应该一看就知道意思,像我英语差的,可以在“Design”上试着填写一些参数,看下视图有什么变化,然后“反标”回xml,看下xml是怎么写的,这样搞多了就知道这些属性怎么用了,熟能生巧:
ok,至此已经完成界面的布局了。
按照前面的“大纲”,我们要实现按钮的点击回调事件。怎么写呢?
虽然我不是搞安卓应用的,但以我的开发经验,一般拿到SDK后,第一件事肯定是“看文档”了!!
安卓/QT都一样,我们有快捷键“Shift+F1”可以弹出帮助文档,因为当前还没有Java的Button类生成,我可以在activity_main.xml上找到“
我们可到它有demo示例:
它提示说,要先在activity_main.xml布局文件里设定一个id,然后在java文件里注册回调函数,这里用到匿名函数,很方便,c++11也有。
把代码添加进去后,发现“江山一片红”,没有加入相应的包,AS就是比较智能,它可以提示有多种解决方案供你选择:
这样,选择“Import class”后,它自动帮你添加“import android.app.Activity;”,AS的自动补全方面很爽。异常代码块/if/else都可以自动添加譬如“Ctrl+Alt+T”,括号下(按Ctrl+shift+空格)能自动强制转换类型等等。
button也能用上述提示的方法来自动添加包,也能通过阅读刚刚的帮助文档来获知应该包含什么包,譬如像下面的:
说真的,工作中代码的编写能力不如学会读懂文档和调试技巧,能快速定位问题。有些bug往往只是修改一两行代码,但排查起来可能是一天甚至更久。
因为我们还没有实现C语言的so,所以先在回调函数里加个“吐司”,产生提示框用于调试。同时把按钮上的文字也修改一下,以实现这样的业务:
默认按钮显示“Led off”,当按下按钮,文本变为“Led on”,再按下提示“Led off”,同时产生“提示框”:
package com.mkelehk.helloworld;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private boolean isLedOn = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = findViewById(R.id.Led);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Code here executes on main thread after user presses button
if(isLedOn == false) {
button.setText("Led off");
Toast.makeText(getApplicationContext(), "Led off", Toast.LENGTH_SHORT).show();
}
else {
button.setText("Led on");
Toast.makeText(getApplicationContext(), "Led on", Toast.LENGTH_SHORT).show();
}
isLedOn = !isLedOn;
}
});
}
}
gradle配置不需要修改,使用默认的即可,或者将targetSdkVersion改为27也行,AndroidManifest.xml也暂时不用修改。
这样我们就实现了一个按钮控件了,而且点击它会有反应。通过adb可以连接真机进行上机调试,下断点单步仿真也行。
最后,如果开发APP过程中,文件目录结构改变了,譬如修改依赖包文件夹名字,它会导致R这个对象报错,除了需要修改java文件,import正确路径外,还要修改AndroidManifest.xml以及app/build.gradle下的applicationId字段,不然虽然能编译通过,但在真机运行中,logcat可能找不到那个进程名称,无法连接socket,也就没有log输出了:
package="com.example.helloworld">
和
defaultConfig {
applicationId "com.example.helloworld"
...
}
根据文章开头的大纲,我们紧跟着就要使用JNI接口构建一个arm64平台的so动态库了。