一粟

为前端工程师写的安卓入门知识

2020.03.07

跨端开发一直都是火热的话题,作为前端开发者,了解一下终端的知识也是有好处的这篇先简单介绍一些安卓一些常用的基础知识和概念。

一、环境配置

安卓环境配置相对来说还比较简单,一般在官网下载Android Studio之后,创建个Hello World项目就是可以顺利编译的。这个过程中Java环境和一些安卓依赖的sdk环境,都是自动帮忙配置好。但是为了让我们能够在命令行里使用类似adb java等命令。还需要配置一下环境变量:

export ANDROID_HOME=~/Library/Android/sdk
export JAVA_HOME=/Applications/Android\ Studio.app/Contents/jre/jdk/Contents/Home
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
export PATH=${PATH}:${JAVA_HOME}/bin

这里命令行最常用的就是adb命令。adb很强大,把安卓手机插到电脑上,可以用adb执行一些常用的操作,节省时间。如果只想使用adb命令,并不需要把整个Android Studio下载下来。可以找个adb文件,放在自己的环境变量里也就可以用了。前提是安卓手机打开了开发者选项的usb调试。

adb常用命令举例:

adb install path/to/apk # 安装电脑上对应目录的apk文件到手机。
adb shell #直接进入手机的shell环境,可以查看手机的文件内容、性能情况、杀掉进程等(很多linux命令都可以使用)
adb push path/to/file /sdcard/ #把电脑文件传到手机上(比如传个电影什么的)
adb pull /sdcard/file ~/movie/ #把手机的文件,传到电脑上

### 还可以模拟手势:(要查看具体坐标值,可以打开开发者选项->指针位置)
adb shell input tap 100 120 #模拟点击,屏幕上横坐标纵坐标分别为100 120的位置,
adb shell input swipe 0 1000 800 600 #模拟滑动,从位置(0,1000)滑动到(800,600)
adb shell input swipe 100 200 100 200 500 #模拟长按,在位置(100,200)长按500毫秒

小技巧:adb模拟手势的一个很好的用途,就是(自动化测试)自动玩游戏。对于一些无脑刷的手机游戏可以模拟手势自动刷。比如之前玩的阴阳师 刷pve副本的时候,其实人的操作很少,但是又不能跳过。就可以做个adb命令的脚本,自动点击。自己就可以看剧或者玩其他的了,放手机在那里刷就可以了。

除了上面举例的adb命令,其实还要其他很多,比如重启机器、停止应用、启动应用、查看安装应用列表、查看电池、截图、录制屏幕等等。有兴趣的同学可以自行探索—>adb常用命令

二、项目目录结构

image-20200309101722006

比如这是创建的一个Hello World项目,代码主要写在Java目录下,res目录用来存放一些资源文件,比如图片和布局的xml文件等,在安卓里面很多样式的实现并没有css那么容易。比如,圆角这种形状其实也是要用一个xml文件来单独定义的。再比如,处理按钮按下和抬起两个状态,css里只用加个选择器就可以了。但是安卓里也需要创建一个叫selector的xml配置文件来配置。

仔细看截图会发现,drawable目录名后面要带v21/v24、hdpi/mdpi/xxhdpi等这些参数。这里不是因为取名偷懒了,这做是为了适配不同的平台和尺寸。

举个例子,v21指的是安卓系统版本21对应的就是安卓5.0。在这个目录下可以使用5.0的新方法,对5.0及以后的版本的机型有效。这样在适配不同版本的机型时,每个目录下都放一个同样文件名的配置文件。系统会自动根据不同机型读取合适的文件,既可以让新机型用上新特性,又避免老机型出现异常。

xhdpi也是类似的原因,是为了适配不同屏幕分辨率而出现的。屏幕分辨率高的设备就用更大的图,更清楚,屏幕分辨率小的设备就用小图,够用,也节省计算性能。但是因为每个目录下都放张图的话,毕竟对安装包体积增加比较多,所以一般还是只放一个目录。

下面的AndroidManifest.xml是非常关键的一个配置文件,所有Activity都需要在里面声明才可以使用,很多静态监听系统广播的方式也是在里面执行监听的。

比如下图是AndroidManifest.xml里的常见配置:让MainActivity监听启动的通知,这样点击App的icon之后MainActivity就会启动了。

image-20200307153509373

三、依赖引用Gradle

Gradle基于 groovy 语言,主要用来管理安卓项目中的一些依赖关系和构建。

常见的依赖管理,比如,打开Hello World项目app下的build.gradle,里面就指明了依赖了哪些库,这些库在构建的时候会被下载到本地。一般依赖的库被放在maven上存储。当然,也可以对本地目录下的jar/aar资源包进行依赖,比如下面的第一行就是依赖libs下所有的.jar文件。

image-20200307164009698

有时为了逻辑的统一和复用,会把一些代码模块抽成单独的module。比如我们的app里房间和首页因为比较复杂抽成了单独module,基础组件webview、下载、csc等复用组件也都单独抽成module。然后被其他的module引用。引用的形式和上面引入外部库类似。

如下图,引入了flutter和webview:

image-20200307165606157

被引用的模块会使用叫com.android.library的插件,标识是个可以被使用的库。而app最顶层的module会使用叫com.android.application的插件,表示是一个app。

这里的gradle引入主要有两种关键字implementationapi

两者的主要区别在于:

implementation的依赖不会传递,比如A组件引用B组件,B组件引用C组件。用implementation的话,A就不能使用C的方法,对C是无感知的。

api 的依赖是可以传递的。同上,如果B对C的引入方式是api的话,A是可以直接使用C的方法。

官方推荐使用implementation来依赖module。这样编译时,底层module发生了变化,对上层module的影响会更小。可以加快编译速度。

Gradle除了处理依赖,还有很多其他的功能:

配置app的参数

 compileSdkVersion 29
 buildToolsVersion "29.0.2"
 defaultConfig {
        applicationId "com.example.myapplication" //包名
        minSdkVersion 15 //最小支持的系统版本号。15 对应 Android 4.0.3
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
 }

同时构建不同类型的apk,常见的比如,因为安卓机型有不同的架构x86 armeabi mips等,如果所有的底层so库都打包在一起,apk会很大。可以在gradle里配置同时编译出适配不同类型的apk,用户下载时只用下载对应类型就可以了。

压缩代码,可以使用ProGuard对代码进行压缩,把很长的命名改成无意义的短命名。但是对于像Activity这种类名,因为会在AndroidManifest.xml里面声明,不能随意进行修改,所以ProGuard也可以配置混淆的白名单,对于特殊的类名不混淆。

对app签名,一般签名也是在gradle配置。当需要发布应用市场时,就需要对app进行签名。签名的意义在于,可以看出一些apk未经授权的修改。当用户安装app升级时,也一定要有同样的签名才能升级成功。

四、四大组件

安卓有四大组件,分别是:Activity、Service、Broadcast、ContentProvider。这四个是安卓系统中非常重要的四个组成部分。

Activity是用于页面展示的,基本上app里每个页面都是一个Activity。提供了一些必要的生命周期,供开发者使用。比如我们的App里,首页、房间、个人中心等,都是Activity。平常我们打开的全屏webview也是单独的一个Activity。Activity提供了一个栈,当一个Activity打开另一个Activity的时候,新的会在旧的上面依次入栈。当用户点击返回按键时,这些Activity会依次出栈展示出来。比如,终端提供的JsBridgebackToWebView(index)可以回退到前几个的页面,原理就是持有这些中间的Activity。根据参数index,把栈顶的几个Activity关掉。

Service顾名思义,是服务。Activity提供了界面交互,但是关闭界面后Activity就会被销毁。有些需要长时间运行的逻辑,就需要交给service了。比如用户长时间下载上传文件、播放音乐等。比如我们的App网络请求,IM通知都是放在一个单独的Service做的。

Broadcast广播,目的是满足 Android应用和系统 以及 Android应用之间 的通信。收发广播消息有点类似“发布-订阅”。只有订阅声明了需要对应的广播,才能收到。这个最初设计的目的当然是更好地服务用户,但是很多App利用这个特性来保活。如果你是一个安卓用户,应该遇到过,打开飞行模式又立刻关掉,一个app突然发了条推送。打开一个A app,B app突然发了条推送。没错他们就是通过广播做到的,监听系统广播,伺机唤醒自己。或是启动的时候发条广播,叫醒兄弟姐妹。当然谷歌也发现了这个现象,新的安卓系统对广播的限制也越来越大。

ContentProvider内容提供程序,可以封装数据,提供安全的数据访问机制。让一个App可以访问另一个App的数据库。常见的比如访问联系人、日历等。也可以用来管理应用自身的存储,比如有些App里面会有多个进程,为了保证进程之间的状态是同步的,也会使用这个来实现跨进程储存一些状态变量。

五、主线程

主线程也是安卓中一个非常关键的概念。不像js只有一个线程,java中可以开很多线程,甚至不同的进程。

当APP启动时,系统会创建一个主线程。这个线程主要负责绘制UI和响应用户的交互。安卓App大部分的交互逻辑都是在这个线程里进行的,所以也被称作UI线程。但是对于一些耗时的操作,比如网络请求、操作数据库是不能放在这里的,需要另起线程进行操作。安卓系统对主线程的响应时间是有限制的,一般情况如果主线程阻塞超过5s,就会在用户界面弹出提示,询问用户是否强制关闭。这个就是ANR(Application Not Responding)。

所以,对于主线程有两点限制:

  1. 不能阻塞UI线程
  2. 不能在非UI线程操作UI控件。

那么,如果要做延迟操作怎么办?比如延迟10s更换文案。不能在UI线程等待,如果另起子线程又不能操作UI,咋整。

安卓提供了主线程的消息循环机制,所以有类似H5的setTimeout来做延迟操作。对应的类叫Handler,可以理解成和setTimeout非常相似。但是Handler依赖线程中已经创建好了消息循环Looper,主线程里系统会创建好Looper。其他自己的创建的子线程默认是没有的,若要使用需要自己创建,否则会报错。Handler对应常用的方法有:post(Runnable) postAtTime(Runnable, long) postDelayed(Runnable, Object, long)

Handler也经常用于切换线程,比如在UI线程接收到了用户的响应,切换到子线程读个数据库,读到结果后再切换回UI线程执行UI操作。

关于安卓的基础知识先介绍到这里。后续会有关于其他的客户端知识介绍。欢迎感兴趣的同学一起交流讨论。

本文首发于 一粟(http://zeyio.com),欢迎转载,但是必须保留本文的署名和链接。

本文永久链接:http://zeyio.com/basic-android-for-web-developer/


沧海一粟,欢迎来访。