基础理论
安卓4层架构
Linux内核层
android 是 Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。
系统运行库层
这一层通过一些C/C++库来为Android系统提供了主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES库提供了3D绘图的支持,Webkit库提供了浏览器内核的支持等。
这一层还有Android运行时库提供了一些核心库,能让开发者使用Java来写Android,Android运行时库中还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程当中,并且拥有一个自己的Dalvik虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的,针对手机内存、CPU性能有限等情况做了优化处理。
应用框架层
主要提供了构建应用程序时可能用到的各种API, Android自带的一些核心应用就是使用这些API完成的,开发者也可以通过使用这些API来构建自己的应用程序。
应用层
所有安装在手机上的应用程序都是属于这一层的
android sdk
Sdk(software development kit)是指被软件工程师用于为特定的软件包、框架、硬件平台等建立应用软件的开发工具集,即软件开发工具包。就像前面配置java环境中的jdk类似,jdk是java的开发工具包,这里的android的sdk是指android为我们提供的软件开发工具包。
android NDK
NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大
NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。
使用NDK,我们可以将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
使用NDK,我们可以将需要保密的应用逻辑使用C开发。毕竟,Java包都是可以反编译的。
ADB (Android Debug Bridge)
全称为Android Debug Bridge,是 Android SDK 里的一个是一个多功能命令行工具,就是一个调试桥,用这个工具可直接操作管理Android模拟器、虚拟机或者真实的Android设备。
它是一个客户端 - 服务器程序,包括三个组件:
客户端
,您可以通过发出adb命令从shell调用客户端。 其他Android工具(如ADT插件和DDMS)也可以创建adb客户端。服务器
,在开发计算机上作为后台进程运行。 服务器管理客户端与在仿真器或设备上运行的adb守护程序之间的通信。后台程序
在每个模拟器或设备实例上作为后台进程运行
主要作用有:
- 在设备上运行Shell命令; 将本地APK软件安装至模拟器或Android设备;
- 管理设备或手机模拟器上的预定端口;
- 在设备或手机模拟器上复制或粘贴文件
项目目录
主项目目录
/app
项目源代码/build
它主要包含了一些在编译时自动生成的文件/gradle
包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle/build.gradle
项目全局的gradle构建脚本 gradle使用gradle.properties
全局的gradle配置文件,这里配置的属性会影响到项目中所有gradle编译脚本/gradlew, /gradlew.bat
这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的/local.properties
用于指定本机中的Android SDK路径,通常内容都是自动生成的,并不需要修改/settings.gradle
用于指定项目中所有引入的模块。由于一般项目中就只有一个app模块,因此该文件中也就只引入了app这一个模块
app项目目录
/app/build
这个目录和外层的build目录类似,主要也是包含了一些在编译时自动生成的文件/app/libs
如果你的项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去/app/AndroidManifest.xml
这是你整个Android项目的配置文件,你在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明/app/build.gradle
这是app模块的gradle构建脚本/app/res/drawable*
都是用来放图片/app/res/mipmap*
都是用来放应用图标的/app/res/values
用来放字符串、样式、颜色等配置的/app/res/layout
文件夹是用来放布局文件
app/build.gradle 详解
// 首先第一行应用了一个插件,
// 一般有两种值可选:com.android.application表示这是一个应用程序模块,
// com.android.library表示这是一个库模块
// 应用程序模块和库模块的最大区别在于,一个是可以直接运行的,一个只能作为代码库依附于别的应用程序模块来运行
apply plugin: 'com.android.application'
android {
// 指定项目的编译版本
compileSdkVersion 29
// buildToolsVersion用于指定项目构建工具的版本
buildToolsVersion "29.0.3"
defaultConfig {
// 指定项目的包名
applicationId "com.example.myapplication1"
// 指定项目最低兼容的Android系统版本
minSdkVersion 28
// targetSdkVersion指定的值表示你在该目标版本上已经做过了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性
targetSdkVersion 29
// versionCode用于指定项目的版本号
versionCode 1
// versionName用于指定项目的版本名
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// 指定生成安装文件的相关配置
buildTypes {
// 指定生成正式版安装文件的配置
release {
// 是否对项目的代码进行混淆
minifyEnabled true
// proguardFiles用于指定混淆时使用的规则文件,这里指定了两个文件,
// 第一个proguard-android.txt是在Android SDK目录下的,里面是所有项目通用的混淆规则,
// 第二个proguard-rules.pro是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
// Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖
// 添加一个库依赖 implementation project(':helper')
// implementation fileTree是一个本地依赖声明,
// 它表示将libs目录下所有.jar后缀的文件都添加到项目的构建路径当中
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 远程依赖声明,androidx.appcompat:appcompat:1.2.0就是一个标准的远程依赖库格式,
// 其中androidx.appcompat是域名部分,用于和其他公司的库做区分;
// appcompat是组名称,用于和同一个公司中不同的库做区分;
// 1.2.0是版本号,用于和同一个库不同的版本做区分
// 加上这句声明后,Gradle在构建项目时会首先检查一下本地是否已经有这个库的缓存,
// 如果没有的话则会去自动联网下载,然后再添加到项目的构建路径当中
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
// testImplementation 是用于声明测试用例库
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
安卓基础
基础知识
- 通常Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖
- 本地依赖可以对本地的Jar包或目录添加依赖关系,
- 库依赖可以对项目中的库模块添加依赖关系,
- 远程依赖则可以对jcenter库上的开源项目添加依赖关系
- Android程序设计讲究逻辑和视图分离,因此是不推荐在活动中直接编写界面的,更加通用的一种做法是,在布局文件中编写界面,然后在活动中引入进来
- 在代码中通过R.(string|drawable|layout).app_name可以获得该字符串的引用。
- 在XML中通过@string/app_name可以获得该字符串的引用
app/build.gradle
比如说Android 6.0系统中引入了运行时权限这个功能,如果你将targetSdkVersion指定成23或者更高,那么系统就会为你的程序启用运行时权限功能- layout文件里
@+id/id_name
这种语法 是定义一个id - 如果应用程序中没有声明主活动,这个程序仍然可以正常安装,只是无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务供其他应用在内部进行调用的,如支付宝快捷支付服务
像素单位 dp、px、pt、sp
的比较
-
dp(dip): device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGA和QVGA 推荐使用这个,不依赖像素。
-
px: pixels(像素). 不同设备显示效果相同,一般我们HVGA代表320x480像素,这个用的比较多。
-
pt: point,是一个标准的长度单位,1pt=1/72英寸,用于印刷业,非常简单易用;
-
sp: scaled pixels(放大像素). 主要用于字体显示best for textsize。
-
extView 的字号最好使用 sp 做单位,而且查看TextView的源码可知 Android 默认使用 sp 作为字号单位。
日志
Android中的日志工具类是Log(android.util.Log
), 在logcat下边看日志,还可以根据标签过滤。不要使用System.out 打印日志!
Log.v()。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。
Log.d()。用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。
Log.i()。用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为数据。对应级别info,比debug高一级。
Log.w()。用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。
Log.e()。用于打印程序中的错误信息,比如程序进入到了catch语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级
布局
基础
android:gravity
来指定文字的对齐方式,可选值有top、bottom、left、right、center等,可以用“|”来同时指定多个值,这里我们指定的center,效果等同于center_vertical|center_horizontal,表示文字在垂直和水平方向都居中对齐setVisibility
修改View的可见性,View可用性如下三种状态:
View.VISIBLE
可见,
View.INVISIBLE
不可见,但仍然占据可见时的大小和位置。
View.GONE
不可见,且不占据空间
LinearLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="2dp"
tools:context=".Main4Activity">
// android:layout_height为0,切android:layout_weight为1,
//表示高度不是height决定,而是vertical方向 flex 1 的效果
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:layout_weight="1" >
<Button
android:id="@+id/btn11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="haha1" />
<Button
android:id="@+id/btn12"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="haha2" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:layout_weight="2" >
<Button
android:id="@+id/btn13"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="haha11" />
<Button
android:id="@+id/btn14"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="haha22" />
</LinearLayout>
</LinearLayout>
RelativeLayout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RelativeLayoutDemoActivity">
<!-- relative_btn1 对其父元素中的左边和Top -->
<Button
android:id="@+id/relative_btn1"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="relative_btn1" />
<!-- relative_btn1 对其父元素中的右边和Top -->
<Button
android:id="@+id/relative_btn2"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:text="relative_btn2" />
<!-- relative_btn3 控件在父元素中的中间位置 -->
<Button
android:id="@+id/relative_btn3"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_centerInParent="true"
android:text="relative_btn3" />
<!-- relative_btn4 相当于 relative_btn3 的上边 ,并且在 relative_btn3 的左边-->
<Button
android:id="@+id/relative_btn4"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_above="@id/relative_btn3"
android:layout_toLeftOf="@id/relative_btn3"
android:text="relative_btn4" />
<!-- relative_btn5 相当于 relative_btn3 的上边 ,并且在 relative_btn3 的右边-->
<Button
android:id="@+id/relative_btn5"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_above="@id/relative_btn3"
android:layout_toRightOf="@id/relative_btn3"
android:text="relative_btn5" />
<!-- relative_btn5 相当于 relative_btn3 的下边 ,并且在 relative_btn3 的右边-->
<Button
android:id="@+id/relative_btn6"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_below="@id/relative_btn3"
android:layout_toRightOf="@id/relative_btn3"
android:text="relative_btn6" />
<!-- relative_btn7 相当于 relative_btn3 的下边 ,并且和relative_btn4 的左边对齐 -->
<Button
android:id="@+id/relative_btn7"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_below="@id/relative_btn3"
android:layout_alignLeft="@id/relative_btn4"
android:text="relative_btn7" />
<!-- relative_btn8 和 relative_btn3 的top 对齐-->
<Button
android:id="@+id/relative_btn8"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:background="@color/blue"
android:layout_alignTop="@id/relative_btn3"
android:text="b8" />
</RelativeLayout>
FrameLayout
又称作帧布局,它相比于前面两种布局就简单太多了,因此它的应用场景也少了很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角。
ConstraintLayout
-
约束布局号称一层布局实现所有效果
-
约束布局ConstraintLayout 是一个ViewGroup,可以在Api9以上的Android系统使用它,它的出现主要是为了解决布局嵌套过多的问题,以灵活的方式定位和调整小部件
-
添加依赖
app/build.gradle
中添加implementation 'com.android.support.constraint:constraint-layout:1.1.3'
-
由于约束布局并不是内置在系统SDK当中的,所以需要把完整的包路径写出来
-
相对定位,一个元素,相对于另外一个
layout_constraintA_toBOf
代表自己的A 对其 目标的B -
百分比,如 layout_constraintHorizontal_bias,就是实现了左右约束,bias值=子View左相关的长度/(子View左相关的长度+其右相关的长度),默认值为0.5,也就是元素在中间居中
-
等分,比如,就2个元素, 一个元素
app:layout_constraintHorizontal_weight="2"
一个元素是app:layout_constraintHorizontal_weight="1"
就是 flex 1 和 flex 2 的效果 -
当多个控件除了头和尾之外彼此之间互相约束,他们之间就形成了一条链,由头和尾决定链的约束位置。
<!-- 约束布局 - 链 --> 默认就是 app:layout_constraintHorizontal_chainStyle "spread" 默认就是 元素平分间距 "packed" 头尾分间距,元素之间没有间距 "spread_inside" 两边没有间距,中间的元素平分
-
分组,是线性布局中,嵌套布局是为了一块东西,所以约束布局有组的概念 ,可以为 Group 设置统一的点击监听、显示、隐藏等等
百分比布局
- LinearLayout本身已经支持按比例指定控件的大小了,因此百分比布局只为Frame-Layout和RelativeLayout进行了功能扩展。
- LinearLayout支持百分比不是直接写
50%
这样,而是android:layout_weight="1"
- 添加依赖
app/build.gradle
中添加implementation 'com.android.support:percent:24.2.1'
活动(Activity)
它是一种可以包含用户界面的组件
,主要用于和用户进行交互。
Activity是一个Android应用程序组件(也称为Android四大组件之一),它提供了一个屏幕,用户可以通过该屏幕进行交互以执行某些操作,例如拨打电话,拍照,发送电子邮件或查看地图。每个活动都有一个窗口,用于绘制其用户界面。窗口通常填满屏幕,但可能比屏幕小,并漂浮在其他窗口的顶部.
Android中的活动是可以层叠的。我们每启动一个新的活动,就会覆盖在原活动之上,然后点击Back键会销毁最上面的活动,下面的一个活动就会重新显示出来。
其实Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
在访问Activity时,必须在manifest中声明此Activity,
// 跳转活动
Intent intent = new Intent(MainActivity.this,Main2Activity.class);
startActivity(intent);
活动状态
1.运行状态当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验
2.暂停状态当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。
3.停止状态当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
4.销毁状态当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
活动的生存期
onCreate()
在活动第一次被创建的时候调用,用于完成活动的初始化操作,如加载布局、绑定事件等
onStart()
在活动由不可见变为可见的时候调用
onResume()
这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态
onPause()
这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用
onStop()
这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行
onDestroy()
这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态,完成释放内存的操作
onRestart()
这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了
分为3种生存期
完整生存期。
活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个活动会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作
可见生存期。活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存
前台生存期。活动在onResume()方法和onPause()方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,我们平时看到和接触最多的也就是这个状态下的活动
活动被回收
应用中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了停止状态,这个时候由于系统内存不足,将活动A回收掉了,然后用户按下Back键返回活动A,会出现什么情况呢?
其实还是会正常显示活动A的,只不过这时并不会执行onRestart()方法,而是会执行活动A的onCreate()方法,因为活动A在这种情况下会被重新创建一次。
活动的启动模式
启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式
standard
模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop
在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例
singleTask
每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例
SingleInstance
这种模式是最特殊的模式,这种模式是为了让不同的app之间可以共享同一个活动,如果你的app想让别的app调用你的某一个界面,就可以用这种模式,这种模式会为你想共享的界面单独创造出一个单独使用的返回栈,不会与别的返回栈共同使用
Application
和Activity一样,都是Android框架的一个系统组件
应用程序每次启动时,系统会为其创建一个application对象且只有一个(单例类),用来存储系统的一些信息,相当于一个容器。
启动application时,系统会创建一个PID(进程ID),所有的activity都在这个进程上运行,在application创建时会初始化一个全局变量,同一个应用的activity,都可以获取到这个变量,也就是说,某一个activity中改变了这个变量,其他activity里也会改变。
activity是UI界面的抽象,而application是应用程序的抽象
Intent
Intent是Android程序中各组件之间进行交互的一种重要方式,
可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。
Intent一般可被用于启动活动、启动服务以及发送广播等场景
// 都是在活动的onCreate方法中
// 传递数据
intent.putExtra("name", "intent-data1");
// 接收数据
Intent intent = getIntent();
String name = intent.getStringExtra("name");
返回上级 数据带回去
// 活动1
// 接收返回结果
startActivityForResult(intent, 1);
// 活动1 重写方法
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if(requestCode == 1){
if(resultCode == RESULT_OK){
String returnData = data.getStringExtra("data-return");
Log.i("main", returnData);
}
}
super.onActivityResult(requestCode, resultCode, data);
}
// 活动2
// 点击返回 和按一下Back键就可以销毁当前的活动一样
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("data-return", "data-return");
setResult(RESULT_OK, intent);
finish();
}
});
// 重写back键
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data-return", "data-return");
setResult(RESULT_OK, intent);
finish();
super.onBackPressed();
}
HTTP
http 是无状态的请求响应的协议。
http2 使用二进制格式,而不是文本格式 多路复用 多重请求响应
HttpUrlConnect是java标准类,用于安卓网络请求。
控件
- 所有控件都是直接或间接继承自View的,
- 所有布局都是直接或间接继承自ViewGroup的
- 自定义控件, 实现一个公用的头部
// 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/blue"
android:layout_width="match_parent"
android:layout_height="36dp">
<Button
android:id="@+id/title_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:text="Back"
android:textColor="#fff"
android:textSize="14sp"
android:background="@color/blue"/>
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="Title Text"
android:textColor="#fff"
android:textSize="16sp"
/>
<Button
android:id="@+id/title_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:text="edit"
android:textSize="14sp"
android:textColor="#fff"
android:background="@color/blue"/>
</LinearLayout>
// 布局主文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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"
tools:context=".ConstraintLayoutDemo2Activity">
<com.hua.huade001android.TitleLayout
android:layout_width="match_parent"
android:layout_height="36dp">
</com.hua.huade001android.TitleLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
// java代码
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
Button back = findViewById(R.id.title_back);
back.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
((Activity)getContext()).finish();
}
});
}
}
Listview
缺点,性能不好,只能纵向滚动,所以以后要使用RecyclerView
。
RecyclerView是Android 5.0推出的
碎片
碎片(Fragment)是一种可以嵌入在活动当中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间。
广播消息机制
广播是一种可以跨进程的通信方式
标准广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。
有序广播是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。
接收系统广播
Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播,时间或时区发生改变也会发出一条广播。
注册广播的方式一般有两种,在代码中注册被称为动态注册,缺点是必须要在程序启动之后才能接收到广播,在AndroidManifest.xml中注册被称为静态注册
动态广播实现 - 实现网络变化的例子
// 1 实现网络接收器
// 网络接收器,每当网络变化,系统发送一个广播,都会执行onReceive方法
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
boolean success = false;
//获得网络连接服务
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
//获取wifi连接状态
NetworkInfo.State state = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
//判断是否正在使用wifi网络
if (state == NetworkInfo.State.CONNECTED) {
Toast.makeText(context, "using wifi", Toast.LENGTH_SHORT).show();
success = true;
}
//获取GPRS状态
state = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();
//判断是否在使用GPRS网络
if (state == NetworkInfo.State.CONNECTED) {
success = true;
}
//如果没有连接成功
if(!success){
Toast.makeText(context,"当前网络无连接",Toast.LENGTH_SHORT).show();
}
}
}
// 2 在活动中使用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String type = "android.net.conn.CONNECTIVITY_CHANGE";
intentFilter = new IntentFilter();
intentFilter.addAction(type);
networkReceiver = new NetworkReceiver();
registerReceiver(networkReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 销毁活动,要结束广播接收
unregisterReceiver(networkReceiver);
}
静态广播
在 android studio 中 新建一个广播,勾选 exported 和 enabled ,Exported属性表示是否允许这个广播接收器接收本程序以外的广播,Enabled属性表示是否启用这个广播接收器,并会自动在manifest文件中添加声明。 <application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver
android:name=".broadcast.BootReceiver"
android:enabled="true"
android:exported="true"></receiver>
自定义发送标准广播
1 创建接收者
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "MyReceiver get", Toast.LENGTH_SHORT).show();
}
}
2 配置到 android manifest
<receiver
android:name=".broadcast.MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.tecent.chat.MY_BROADCAST" />
</intent-filter>
</receiver>
3. 活动中,加一个按钮点击事件,触发广播
// 点击按钮,发送广播
Button btn = findViewById(R.id.btn_b);
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent("com.tecent.chat.MY_BROADCAST");
sendBroadcast(intent);
}
});
发送有序广播
sendOrderedBroadcast()方法接收两个参数,第一个参数仍然是Intent,第二个参数是一个与权限相关的字符串,这里传入null就行了
为了处理顺序,可以加优先级字段
<receiver
android:name=".broadcast.MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="com.tecent.chat.MY_BROADCAST" />
</intent-filter>
</receiver>
本地广播
导包 implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
1.发送本地广播比发送系统全局广播将会更加高效
2.本地广播无法静态广播
3.本地广播不会数据泄露安全问题
数据持久化
文件存储
Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。方法接收两个参数,
第一个是文件名,在文件创建的时候使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data//files/目录下的。
第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件
// 保存数据到本地文件
public void save(){
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write("huahuadavids");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(writer != null){
try {
writer.close();
Log.v("main", "write end");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
SharedPreferences存储
SharedPreferences是使用键值对的方式来存储数据的
SharedPreferences文件都是存放在/data/data//shared_prefs/目录下的
// 将数据存储到SharedPreferences
public void save1(){
// getShared-Preferences()方法指定SharedPreferences的文件名为data,
// 生产文件的路径为 /shared_prefs/data.xml
// 并得到了SharedPreferences.Editor对象
SharedPreferences.Editor editor = getSharedPreferences("data", 0).edit();
editor.putString("name", "huahuadavids");
editor.putInt("age", 34);
editor.putBoolean("newbee", true);
editor.apply();
Toast.makeText(LoginActivity.this, "success!!", Toast.LENGTH_SHORT).show();
}
// 从SharedPreferences中取数据
public void get1(){
SharedPreferences share = getSharedPreferences("data", 0);
String name = share.getString("name", "");
Log.v("main", name);
}
SQLite数据库存储
SQLite 一个非常流行的嵌入式数据库,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。Android 运行时环境包含了完整的 SQLite。JDBC 会消耗太多的系统资源,所以 JDBC 对于手机这种内存受限设备来说不合适。数据库文件会存放在/data/data/package name/databases/
目录下。
SQLiteOpenHelper
是一个抽象类,使用它需要创建一个自己的帮助类去继承它。有两个抽象方法onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
getReadableDatabase()和getWritableDatabase()
。这两个实例方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。
可以直接使用 execSQL
和 rawQuery
方法执行sql
public class DatabaseHelper extends SQLiteOpenHelper {
//创建数据库sql语句 并 执行
public static final String sql = "create table user(id integer primary key, name text)";
private Context mcontext;
//带全部参数的构造函数,此构造函数必不可少
public DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mcontext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(sql);
Toast.makeText(mcontext, "create success!!", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public class DemoActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
dbHelper = new DatabaseHelper(this, "db-hua", null, 1);
// 多次点,不会重复提示,数据库创建成功,因为oncreate只会执行一次
sql.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了。
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues val = new ContentValues();
val.put("id", 1);
val.put("name", "nina");
db.insert("user", null, val);
val.clear();
val.put("id", 2);
val.put("name", "tina");
db.insert("user", null, val);
Toast.makeText(LoginActivity.this, "插入数据成功", Toast.LENGTH_SHORT).show();
// 更新数据
ContentValues val = new ContentValues();
val.put("name", "sasa");
db.update("user", val, "id = ?", new String[]{
"1"});
// 删除数据
db.delete("user", "id = ?", new String[]{
"1"});
// 查询数据
Cursor cursor = db.rawQuery("select * from user", null);
if(cursor.moveToFirst()){
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
Log.v("main", name);
}while (cursor.moveToNext());
}
cursor.close();
}
});
}
}
LitePal
是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式
dependencies {
implementation 'org.litepal.guolindev:core:3.2.1'
}
运行时权限
public class contentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content);
Button btn1 = findViewById(R.id.rper);
bindClick1(btn1);
}
public void bindClick1(Button btn) {
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ContextCompat.checkSelfPermission(contentActivity.this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
call();
}else {
ActivityCompat.requestPermissions(contentActivity.this, new String[]{
Manifest.permission.CALL_PHONE
}, 1);
}
}
});
}
private void call() {
Toast.makeText(contentActivity.this, "xiaohua", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call();
}else {
Toast.makeText(contentActivity.this, "您拒绝了权限哦", Toast.LENGTH_SHORT).show();
}
}
}
}
内容提供器
内容提供器用于不同app之间实现数据共享,允许一个程序访问另一个中的数据,还能保证被访数据的安全性。使用内容提供器是Android实现跨程序共享数据的标准方式。
使用方法,只需要获取到该应用程序的内容URI,然后借助ContentResolver进行CRUD操作就可以了。
一个标准的URI,比如 content://com.demo.app.provider/table1
content://com.demo.app.provider/*
匹配任意表
content://com.demo.app.provider/table1/#
匹配任意表任意一行数据
参考资料
- 第一行代码:Android(第2版)
- Android SDK目录含义介绍
- Android访问权限大全
- 一篇文章讲清楚Gradle与Gradle Wrapper的区别
- 约束布局ConstraintLayout看这一篇就够了
- Android 四种布局: FrameLayout、ConstraintLayout、LinearLayout、RelativeLayout
- 安卓ListView的使用
- Android AndroidX的迁移
- Android学习之RecyclerView的使用
- Android 完美解决WebView缺陷
- 详解安卓广播机制
- Android总结篇系列:Android广播机制
- 安卓中广播机制
- NDK开发
- Android 组件化方案,从入门到精通。apply plugin: ‘com.android.application’