智能终端与移动应用开发-实验一

[TOC]

智能终端与移动应用开发:实验一

创建安卓HelloWorld工程,仔细观察工程结构

工程结果截图

image-20201013002914661

创建工程

  • 打开 Android Studio,选择 start a new Android Studio project
  • 选择项目模版:选择 Phone and Tablet 中的 Basic Activity
  • 配置这个项目:
    • Name: demo
    • Language: Java
    • Minimum SDK: API 29: Android 10.0(Q) —— 最低版本要求,安卓10,豪横,哈哈。
  • 点击 Finish,项目开始自动创建。安安静静等着CPU狂转。
  • /Users/sevdaisy/AndroidStudioProjects/Demo 就是刚刚创建好的工程

个人VSCode死忠粉 + 不是很喜欢Android Studio

我想试试在VSCode上完成最基础的安卓开发配置

能实现安卓工程的编辑,预览,测试即可。

更多其他功能再去Android Studio上去做

VS code 的 Android 基础配置

参考网址:https://www.jianshu.com/p/378930364493

插件安装

  • adelphes.android-dev-ext 必需 Android 基础
  • dannark.androidlauncher 次要 安卓模拟器
  • flimberger.android-system-tools 不懂,装着玩玩儿

建立 Task.json

  • F1唤出VSCode命令栏,选择>Tasks: configure Default Build Task 配置默认生成任务
  • 选择 创建task.json再选择 Others
  • 自动打开新建的 task.json,用下列代码复写这个文件
{"version":"2.0.0","tasks":[{"label":"AppSignKey","type":"shell","command":"keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias","windows":{"command":"cd gitoscandroid; del my-release-key.jks; keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias"},"group":"test","presentation":{"reveal":"always"}},{"label":"CleanApp","type":"shell","command":"${workspaceRoot}/gradlew clean","windows":{"command":"${workspaceRoot}\\gradlew.bat clean"},"group":"test","presentation":{"reveal":"always"}},{"label":"BuildDebugApp","type":"shell","command":"${workspaceRoot}/gradlew assembleDebug","windows":{"command":"${workspaceRoot}\\gradlew.bat assembleDebug"},"group":"test","presentation":{"reveal":"always"}},{"label":"InstallDebugApp","type":"shell","command":"${workspaceRoot}/gradlew installDebug","windows":{"command":"${workspaceRoot}\\gradlew.bat installDebug"},"group":"test","presentation":{"reveal":"always"}},{"label":"InstallReleaseApp","type":"shell","command":"${workspaceRoot}/gradlew installRelease","windows":{"command":"${workspaceRoot}\\gradlew.bat installRelease"},"group":"test","presentation":{"reveal":"always"}}]}
  • 上述JSON提供了
    • AppSignKey 通过命令行生成签名文件
    • CleanApp 通过命令行清除生成目录
    • BuildDebugApp 通过命令行编译调试APP
    • InstallDebugApp 通过命令行把调试APP安装到手机或虚拟机
    • InstallReleaseApp 通过命令行安装签名后的发布APP

测试 task 是否正常 :: AppSignKey

  • F1唤出VSCode命令栏,选择>Tasks: Run Test Task 运行测试任务
  • 选择任务:AppSignKey
  • 需要在VSCode的终端交互输入一堆信息
    • 据说最关键的是设置的那个密码
    • 我此次设置的是 zyy20201010
    • 据说签名发布的时候需要用到
  • 成功后应该在项目根文件夹生成一个my-release-key.jks文件

测试 task 是否正常 :: BuildDebugApp

  • 直接报错:JAVA_HOME is not set and no 'java' command could be found in your PATH.
  • 那咱就不用它默认的gradle了,自己装一个gradle来耍耍。顺便配一下阿里源

安装 Gradle

  • 使用 Homebrew 安装 brew install gradle

  • 如果网络出问题的话:

    • 自己根据命令行中展示的资源网址,去下载资源
    • 切换到Homebrew的下载缓存目录$HOME/Library/Caches/Homebrew/downloads
    • 下好的文件代替下了一点点的**.incomplate文件**
    • 记得 ^c 终止另一边正在“爬山”的brew install gradle
% ls | grep gradle-6.5.1-all.zip                                                                                          
e9cec0f187339b8644e8dd2c19f5a6bb0cd931d2f5e7543836e3742a8e3648ad--gradle-6.5.1-all.zip.incomplate
% cp -vf gradle-6.5.1-all.zip e9cec0f187339b8644e8dd2c19f5a6bb0cd931d2f5e7543836e3742a8e3648ad--gradle-6.5.1-all.zip
% brew install gradle # 重新开始安装

配置Gradle

cd $HOME/.gradle
cat > gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# systemProp.http.proxyHost=127.0.0.1
# systemProp.http.proxyPort=10800
# systemProp.https.proxyHost=127.0.0.1
# systemProp.https.proxyPort=10800
systemProp.file.encoding=UTF-8
org.gradle.warning.mode=all
^c
cat > init.gradle
allprojects {
repositories {
mavenLocal()
maven { url 'http://maven.aliyun.com/nexus/content/repositories/central/' }
}
}
^c

重测task 是否正常 :: BuildDebugApp

  • 更改 VScode配置文件 task.json 中的 gradlewwhich gradle 的值
  • 新的task.json如下
{"version":"2.0.0","tasks":[{"label":"AppSignKey","type":"shell","command":"keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias","windows":{"command":"cd gitoscandroid; del my-release-key.jks; keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias"},"group":"test","presentation":{"reveal":"always"}},{"label":"CleanApp","type":"shell","command":"/usr/local/bin/gradle clean","windows":{"command":"${workspaceRoot}\\gradlew.bat clean"},"group":"test","presentation":{"reveal":"always"}},{"label":"BuildDebugApp","type":"shell","command":"/usr/local/bin/gradle assembleDebug","windows":{"command":"${workspaceRoot}\\gradlew.bat assembleDebug"},"group":"test","presentation":{"reveal":"always"}},{"label":"InstallDebugApp","type":"shell","command":"/usr/local/bin/gradle installDebug","windows":{"command":"${workspaceRoot}\\gradlew.bat installDebug"},"group":"test","presentation":{"reveal":"always"}},{"label":"InstallReleaseApp","type":"shell","command":"/usr/local/bin/gradle installRelease","windows":{"command":"${workspaceRoot}\\gradlew.bat installRelease"},"group":"test","presentation":{"reveal":"always"}}]}
  • 重新用 **F1 ** >Tasks: Run Test Task 选择 BuildDebugApp
  • 等了一会儿,EveryThing is OK,成功

设置任务快捷键

  • ~/Library/Application Support/Code/User/keybindings.json 中添加如下内容
{"key":"F6","command":"workbench.action.tasks.runTask","args":"InstallReleaseApp"},{"key":"F7","command":"workbench.action.tasks.runTask","args":"BuildDebugApp"},{"key":"F8","command":"workbench.action.tasks.runTask","args":"CleanApp"},{"key":"F9","command":"workbench.action.tasks.runTask","args":"AppSignKey"},

配置调试文件launch.json

注意这里添加”preLaunchTask”: “InstallDebugApp”,意思是在执行Android launch之前,先安装app

{"version":"0.2.0","configurations":[{"type":"android","request":"launch","name":"Android launch","appSrcRoot":"${workspaceRoot}/gitoscandroid/src/main","apkFile":"${workspaceRoot}/gitoscandroid/build/outputs/apk/debug/app-debug.apk","adbPort":5037,"preLaunchTask":"InstallDebugApp"},{"type":"android","request":"attach","name":"Android attach","appSrcRoot":"${workspaceRoot}/gitoscandroid/src/main","adbPort":5037,"processId":"${command:PickAndroidProcess}"}]}

使Java语法插件生效 失败

# 在 app/.classpath 中添加下面这行
<classpathentry kind="src" path="app/src/main/java" />

痛定思痛,决定了解一波 Andoid + Gradle

先说结论:

  • 用VSCode直接在安卓项目内完成包的调用,别想了。
  • 用VScode完成安卓的代码编写,Gradle运行,包括编译和安装apk等,都没有问题
  • 之后的开发中,平时的编辑就使用VScode,等需要查询包函数的时候,再去AS的界面临时查询一下函数。

Android 工程结构

image-20201012222005840

  • 文件夹 app
    • Java项目本体
    • 如果用 Gradle 来创建Java项目:gradle init --type java-application
    • 可以看到创建完的项目,和这个 app文件夹 长得一模一样
    • 所以这个文件夹的结构,就是标准的,Java-applicationJava应用 的结构
    • 包括,源码,外部包,静态资源,编译生成可执行文件,gradle自动编译脚本文件,Java项目基础配置,等文件。
  • 文件夹 gradle.gradle
    • 安卓是用 Gradle 这个工具来辅助工程的构建的。
    • gradle文件夹 是 Gradle本地支持文件,是这个项目的Gradle引擎本体
    • .gradle隐藏文件夹 是 Gradle 生成的本地编译文件,可以理解为Gradle编译缓存
  • 文件夹 build,编译生成文件,不重要,不用管它。
  • 文件夹 .idea .vscode .setting,都是IDE配套的辅助配置文件,也不重要。
    • 比较特殊的是 .idea,因为安卓项目的官方支持IDEAndroid Studio
    • .idea 就是给 Android StudioIntellij IDEA 用的配置文件
    • 很多包的依赖配置都是写在这个 .idea 里的
    • 依赖具体下载和路径配置都是由 Gradle 负责,生成在 .idea 里的
  • 项目根目录下还有林林总总的配置文件,不再赘述

Gradle

  • W3Cschool 学习了两天的【Gradle教程,Gradle用户指南中文版,Android Gradle用户指南】
  • 安卓项目使用的插件是 com.android.application,和普通Java项目的Gradle插件 java,application 是冲突的。
  • Gradle 每在一个项目启动的时候都会去尝试下载 Gradle 所需版本的 min/all .zip文件。
    • 官网的链接在国内下载是挺慢的,偶尔还容易失败
    • gradle/wrapper/gradle-wrapper.properties 是相关的配置文件
    • distributionUrl 是,将要下载的目标gradle.zip
    • 将这个distributionUrl在浏览器中打开,下载保存到本地的固定位置。然后复制下载到本地的ZIP文件的绝对路径
    • 更新distributionUrl的值为 “file://%s” % ( absolute_path_of_zip )
    • 就可大大加速gradle的Server Connect
# gradle/wrapper/gradle-wrapper.properties 文件内容
# Sat Oct 10 10:56:46 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=file\:///Users/sevdaisy/.gradle/wrapper/dists/gradle-6.1.1-all.zip
  • Java文件的依赖配置,最原生的方式就是.classpath文件。Gradle可以帮忙来填充这个文件的内容。从而实现依赖的自动配置。
  • Gradle 的 插件 'java'/'application' ,提供的 task:build 就是特别老实地填充.classpath文件内容。
  • 而安卓用的Gradle插件 'com.android.application' ,和上述插件是冲突的,同时它十分倔强的修改并保持 .classpath 为神奇的短短几行,别说配置 kind:lib 了,它连 kind:src 都不肯配!我一改,它一运行task,就给改回去。
  • 这就是为什么在VSCode的安卓插件和JavaPak插件都在的时候仍无法实现包引用定位的核心问题。
  • 或许,其实可以有办法配置的吧,比如,重写'com.android.application' 提供的 task:build 什么的,相关的gradle语法支持我是查到了,但是,不想再搞了,算了算了。
  • 最后,重复一下结论
    • 用VSCode直接在安卓项目内完成包的调用,别想了。
    • 用VScode完成安卓的代码编写,Gradle运行,包括编译和安装apk等,都没有问题
    • 之后的开发中,平时的编辑就使用VScode,等需要查询包函数的时候,再去AS的界面临时查询一下函数。
  • 不喜欢 Android Studio 的原因,除了很多配置和热键的重新学习/配置成本太高之外,最重要的原因就是,它的原生界面实在是太丑了

编写Java,完成指定需求

需求截图

image-20201012233038502

核心源代码

/**
* @Author: SevDaisy十七散人
* @Date: 2020-10-09 15:55:20
*/
package zyy;

import com.huaban.analysis.jieba.JiebaSegmenter;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

class EnglishNode implements Comparable<EnglishNode> {
String content;

public EnglishNode(String content) {
this.content = content;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((content == null) ? 0 : content.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
EnglishNode other = (EnglishNode) obj;
if (content == null) {
if (other.content != null) return false;
} else if (!content.equals(other.content)) return false;
return true;
}

@Override
public int compareTo(EnglishNode o) {
return this.content.compareToIgnoreCase(o.content);
}

@Override
public String toString() {
return "EnglishNode [content=" + content + "]";
}
}

class ChineseNode implements Comparable<ChineseNode> {
String content; // 词语内容
int cnt; // 出现次数

public ChineseNode(String content) {
this.content = content;
this.cnt = 1;
}

@Override
public int compareTo(ChineseNode o) {
if (this.cnt < o.cnt) return 1; else if (
this.cnt == o.cnt
) return 0; else return -1;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((content == null) ? 0 : content.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
ChineseNode other = (ChineseNode) obj;
if (content == null) {
if (other.content != null) return false;
} else if (!content.equals(other.content)) return false;
return true;
}

@Override
public String toString() {
return "ChineseNode [cnt=" + cnt + ", content=" + content + "]";
}
}

public class Week1 implements Runnable {
private char type;
private static int count_的 = 0;

private static ArrayList<ChineseNode> wordMap_B = new ArrayList<ChineseNode>();

public static int checkLetter(char x) {
int x_ = (int) (x);
if (Character.isLetter(x)) {
if (x_ >= 65 && x_ <= 122) return 1; else if (
x_ >= 0x4E00 && x_ <= 0x9FA5
) return 2;
} else if (x_ == (int) '’' || x_ == (int) '\'') return 1;
return 0;
}

public static int checkLetter_B_count_的(char x) {
int x_ = (int) (x);
if (x_ == (int) '的') {
Week1.count_的++;
}
if (Character.isLetter(x)) {
if (x_ >= 65 && x_ <= 122) return 1; else if (
x_ >= 0x4E00 && x_ <= 0x9FA5
) return 2;
} else if (x_ == (int) '’' || x_ == (int) '\'') return 1;
return 0;
}

public static ArrayList<EnglishNode> A() {
String inStr;
StringBuilder sWord = new StringBuilder();
ArrayList<EnglishNode> wordMap = new ArrayList<EnglishNode>();
try (
BufferedReader inFile = new BufferedReader(
new FileReader("asset/Z01-Example.txt")
);
) {
while ((inStr = inFile.readLine()) != null) {
sWord = new StringBuilder();
for (char k : inStr.toCharArray()) {
if (Week1.checkLetter(k) == 1) {
sWord.append(k);
} else if (sWord.length() > 0) {
wordMap.add(new EnglishNode(sWord.toString()));
// System.out.println(" >>> " + sWord.toString());
sWord = new StringBuilder();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
wordMap = new ArrayList<EnglishNode>(new HashSet<EnglishNode>(wordMap)); // 利用 HashSet 去除重复元素
Collections.sort(wordMap); // 利用集合排序,按字符串字典降序排成降序,忽略大小写
wordMap.forEach(
x -> {
// System.out.println(x);
}
);
try (
BufferedWriter out = new BufferedWriter(
new FileWriter(new File("asset/A_dict.txt"))
)
) {
for (EnglishNode s : wordMap) {
out.write(s.content);
out.write(System.lineSeparator());
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
return wordMap;
}

public static ArrayList<ChineseNode> B() {
JiebaSegmenter segmenter = new JiebaSegmenter();
String inStr;
StringBuilder sWord;
ArrayList<String> wordList;
ArrayList<ChineseNode> wordMap = new ArrayList<ChineseNode>();
Week1.wordMap_B = wordMap;
try (
BufferedReader inFile = new BufferedReader(
new FileReader("asset/Z01-Example.txt")
);
) {
while ((inStr = inFile.readLine()) != null) {
sWord = new StringBuilder();
for (char k : inStr.toCharArray()) {
if (Week1.checkLetter_B_count_的(k) == 2) {
sWord.append(k);
} else if (sWord.length() > 0) {
wordList =
(ArrayList<String>) segmenter.sentenceProcess(sWord.toString());
wordList.forEach(
x -> {
ChineseNode p = new ChineseNode(x);
if (Week1.wordMap_B.contains(p)) {
Week1.wordMap_B.get(Week1.wordMap_B.indexOf(p)).cnt++;
} else {
Week1.wordMap_B.add(p);
}
}
);
sWord = new StringBuilder();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// ChineseNode 不需要用 HashSet 来去重
// wordMap = new ArrayList<ChineseNode>(new HashSet<ChineseNode>(wordMap));
Collections.sort(wordMap); // 利用集合排序,按字符串字典降序排成降序
// System.out.println(wordMap);
wordMap.forEach(
x -> {
// System.out.println(x);
}
);
try (
BufferedWriter out = new BufferedWriter(
new FileWriter(new File("asset/B_dict.txt"))
)
) {
for (ChineseNode s : wordMap) {
out.write(String.format("%s : %d", s.content, s.cnt));
out.write(System.lineSeparator());
}
out.write(String.format("的 : %d", Week1.count_的));
out.write(System.lineSeparator());
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
return wordMap;
}

public static void C() {
String inStr;
ArrayList<Integer> graph_size_list = new ArrayList<Integer>();
Boolean graph_first_line = true;
int graph_i = -1;
try (
BufferedReader inFile = new BufferedReader(
new FileReader("asset/Z01-Example.txt")
);
) {
while ((inStr = inFile.readLine()) != null) {
if (graph_first_line) {
if (inStr.length() != 0) {
graph_i++;
graph_first_line = false;
int line_size = 0;
Boolean english_word_f = false;
for (char k : inStr.toCharArray()) {
if (Week1.checkLetter(k) == 1) {
if (!english_word_f) {
english_word_f = true;
line_size++;
System.out.printf(
"English Word Start! %d:%c\n",
line_size,
k
);
}
} else if (Week1.checkLetter(k) == 2) {
line_size++;
System.out.printf("%d:%c\n", line_size, k);
english_word_f = false;
}
}
graph_size_list.add(line_size);
}
} else {
if (inStr.length() != 0) {
int line_size = graph_size_list.get(graph_i);
Boolean english_word_f = false;
for (char k : inStr.toCharArray()) {
if (Week1.checkLetter(k) == 1) {
if (!english_word_f) {
english_word_f = true;
line_size++;
System.out.printf(
"English Word Start! %d:%c\n",
line_size,
k
);
}
} else if (Week1.checkLetter(k) == 2) {
line_size++;
System.out.printf("%d:%c\n", line_size, k);
english_word_f = false;
}
}
graph_size_list.set(graph_i, line_size);
} else {
graph_first_line = true;
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
for (int i = 0; i <= graph_i; i++) {
System.out.printf("第 %d 段是 %d 字\n", i + 1, graph_size_list.get(i));
}
try (
BufferedWriter out = new BufferedWriter(
new FileWriter(new File("asset/C_graph_size.txt"))
)
) {
for (int i = 0; i <= graph_i; i++) {
out.write(
String.format("第 %d 段是 %d 字\n", i + 1, graph_size_list.get(i))
);
out.write(System.lineSeparator());
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}

public Week1(char type) {
this.type = type;
}

@Override
public void run() {
switch (this.type) {
case 'A':
Week1.A();
break;
case 'B':
Week1.B();
break;
case 'C':
Week1.C();
break;
default:
break;
}
}

public static void main(String[] args) {
new Thread(new Week1('A')).start();
new Thread(new Week1('B')).start();
new Thread(new Week1('C')).start();
}
}

分析与回顾

先说缺点

  • ChineseNode 和 类EnglishNode 还是有相当一部分代码重复,属于冗余的。
  • 主类Week1 中,static的修饰有些过于滥用的嫌疑,包括 A() B() C() ArrayList<ChineseNode> wordMap_B都有滥用了static的嫌疑。只是一开始开发时写的顺手了后面懒得改。
  • 关于如何实现多线程,额,不知道自己这个写法是否OK,心里稍稍有点没谱。
  • 严格来说,部分的try-catch代码仍是可以优化的
    • 比如,如果发现没有asset文件夹,要不要自动创一个,或者程序可以怎么报错然后再退出。

再说优点

  • 整个项目的结构设计还是非常令我自己满意的。
  • 项目整体采用【src,bin,lib,asset】的分块设计
    • lib中放jieba分词的jar/rar包,这个包网上不是很好找,
    • 我保留了一个自己的链接。下载链接
    • asset中放静态资源Z01-example.txt、以及ABC三个函数各自生成的结果文件
  • 核心业务逻辑:读取文件,解码信息,数据清洗,结果保存
  • 读取文件内容,使用了 try-with-resouce 的写法,简单方便安全易读。
  • 解码信息,核心在于判断这个char到底是什么字符。
    • 这部分功能专门抽离为类静态函数checkLetter()
    • 首先调用了官方函数Character.isLetter(),过滤了标点符号
    • 其次通过简单方便易读的if-elseif-... 就可以添加对中文汉字的归类规则和对英文52个字母的归类规则
    • 后期再想添加规则补丁,也就是添加 elseif语句 的事情,简单方便易读
  • 数据清洗,为了方便,设计了专门的【类ChineseNode ,类EnglishNode
    • 美中不足就是这两个类的代码有些冗余,可以写个父类MyNode
    • 数据的去重部分,使用了,arraylist 转 hashset 再转 arraylist 的写法
      • hashSet 判断元素是否相同,需要同时重写 equals()hashCode() 函数
      • 良好的习惯是,重写了equals(),就应该重写相应的hashCode()
    • 方便是方便了,不知道会不会特别影响性能。
    • 主要是,都是List对象,我选择相信官方代码的性能(还是对这些List类的源码不够熟悉
  • 结果输出,
    • 尝试了(new ArrayList).forEach(),和,以lambda表达式(闭包函数)为函数参数的写法
    • 新奇,有趣,方便,好玩。