【Mark 】AndroidStudio_移动应用开发

  • 什么是 Android?

安卓是一种基于Linux内核(不包含GNU组件)的自由及开放源代码的操作系统
主要使用于移动设备,如智能手机和平板电脑,由美国Google公司和开放手机联盟领导及开发
----百度

Android开发环境的配置与准备


一个 Project 可以包含多个 Module,每一个 Module 都对应一个软件
采用 Java 语言开发 Android 应用

SDK

  • 更新 sdk 通过 SDK Manager
    NDK 是底层开发用的,暂时不管

Genymotion

Virtural Box

  • VirtualBox 是一款开源虚拟机软件,号称最强免费虚拟机软件,
    可以在当前运行的系统上构建出一台虚拟电脑,
    在这台虚拟电脑上可以安装系统和软件,就像真实的电脑一样操作
    20.2.14

链接 联想thinkpad笔记本电脑怎么开vt虚拟化.
(我并没有开BIOS的VT的虚化,竟也能用虚拟机…可能是以前开过不知道?)

  • 查看是否已经开虚拟化,打开任务管理器 > 性能 > CPU > 虚拟化 : 已启用

build.gradle 文件

  • 完成上述的许多步骤以后,如果 AndroidStudio 中仍会有许多 Error 的出现,
    根本原因可能是顶级 build.gradle 文件中的问题
    可更改为如下:
buildscript {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/jcenter'}
        mavenLocal()
        mavenCentral()
        //google()
        //jcenter()	//我的并没有将这两条语句注释,不然会出问题
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
    }
}

allprojects {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/jcenter'}
        mavenLocal()
        mavenCentral()
        //google()
        //jcenter()	//还有这条
        
    }
}

遇到的问题1:

Gradle sync failed: Cause: invalid type code: 85 
Consult IDE log for more details (Help | Show Log) (22 s 73 ms)

解决1 :
链接 下载新的Gradle.zip文件替换.

问题2 插件无法更新
AndroidStudio Firebase services 3.6更新报错.
用浏览器打开报错给的网址,直接下载插件压缩包
然后在 Installed 右边的设置 Install Plugin from Disk,导入压缩包即可
网址 链接.

http://plugins.jetbrains.com/pluginManager/?action=download&id=com.google.services.firebase&build=AI-192.7142.36&uuid=325b0776-a0e1-4765-8883-2a3dfe7cb602

问题3 删除后的应用无法安装
原因: 已经安装过了
解决: cmd 中

adb uninstall 应用程序包名

链接 Android开发环境的设置与准备_问答. 20.2.17

Android 概述


  • 什么是 API?
    Application Programing Interface (应用程序编程接口)
    简单来说就是封装了复杂操作的函数
    链接 什么是API?.

  • 什么是MSDN?
    操作系统的API有很多,需要一个说明文档
    MSDN就是Windows API 的说明文档

Android 版本

  • Android 版本,目前
    平台版本10,API 级别 29,版本号 (VERSION_CODE) Q
    Android 主要用于移动设备

Android平台结构
Android 平台结构,基于 Linux 内核 (Kernel) 的分层结构
五层
① System Apps--------------------------------------------(系统应用)
② Java API FrameWork ---------------------------------(Java API 框架)
③ Native C/C++ Libraries、Android Runtime ------(C/C++原生库、ART)
④ Hardware Abstraction Layer -------------------------(HAL 硬件抽象层)
⑤ Linux Kernel----------------------------------------------(Linux 内核)
在这里插入图片描述

上层功能可以依靠 Linux 内核来执行底层功能
硬件抽象层 (HAL),提供标准界面,显示硬件设备功能

Android 5.0 以上,
每个应用都在其自己的进程中运行,都有其自己的 Android Runtime (ART) 实例
DEX (Dalvik Executable) 文件是一种专为 Android 设计的字节码格式
编译工具链 (如 Jack) ,
将 Java 源代码 编译为 DEX 字节码;
删1

  • 原生 C/C++ 库
    许多核心 Android 系统组件和服务(例如 ART 和 HAL)构建自 原生代码
    代码在 以 C 和 C++ 编写的原生库中

  • Java API 框架
    以Java语言编写的API
    以此使用 Android OS 的整个功能集

  • 系统应用
    Android 系统随附的应用,
    和第三方应用一样,没有特殊的状态

APK

  • Android SDK 工具将你的 代码 + 所有数据资源 等 编译到一个APK中
    Android 软件包 (Android application package),.apk
    一个 APK 文件包含一个 Android 应用的所有内容,
    它是 基于 Android 系统的设备 用来安装应用的文件

Android 应用的运行方式和运行权限

  • Android 操作系统是一种多用户 Linux 系统,其中 每个应用都是一个不同的用户
    系统 分配 Linux 用户 ID(该 ID 仅由系统使用,应用并不知晓)
    系统为应用中的 所有文件 设置权限,只有 用户 ID 匹配才能访问
  • 每个进程 都具有自己的虚拟机 (VM)
    因此应用代码是在 与其他应用隔离 的环境中运行
  • 默认情况,每个应用都 在其自己的 Linux 进程 内运行
  • Android 会在需要执行 任何应用组件 时启动相应进程,
    然后在不需要时关闭该线程,实现 最小权限原则
  • 可以安排两个应用共享同一 Linux 用户 ID,这样,
    它们能够相互访问彼此的文件,可在同一 Linux 进程中运行;
    应用可以请求访问设备数据的权限

组件

组件 (Component)
组成 Android 应用
可以单独调用,每个组件都是一个不同的入口点
共有四种不同的应用组件类型:
Activity、服务 Service、广播接收器 BroadcastReceiver、内容提供程序 ContentProvider

  • Activity 表示具有用户界面的 单一屏幕
    一个应用可有多个 Activity,它们之间互相独立

  • 服务是一种在 后台运行 的组件,用于执行长时间运行的操作或为远程进程执行作业
    其他组件可以启动服务,让其运行或与其绑定以便与其进行交互

  • 广播接收器,用于响应系统范围广播通知
    不显示用户界面,会创建状态栏通知;作为通向其他组件的“通道”;
    广播接收器作为 BroadcastReceiver 的子类实现,并且每条广播都作为 Intent 对象进行传递

  • 内容提供程序管理一组 共享 的应用数据
    其他应用可以通过内容提供程序查询、修改数据 (需要权限)
    内容提供程序作为 ContentProvider 的子类实现,并且必须实现让其他应用能够执行事务的一组标准 API

启动组件
Android 系统设计的独特之处,任何应用都可通过 Android 系统间接启动其他应用的组件
向系统传递一则消息,说明想启动特定组件的 Intent,系统随后便会启动该组件
当系统启动某个组件时,会启动该应用的进程 (如果尚未运行),并实例化该组件所需的类

  • Activity、服务、广播接收器,这三种组件类型通过名为 Intent 的异步消息进行启动
    Intent,异步消息机制,一个载体
    Intent 使用 Intent 对象创建,它定义的消息用于启动特定组件或特定类型的组件
    可以指定要执行操作的数据的 URI(对于广播组件,只能定义要广播的通知)
  • 内容提供程序 组件,只会在成为 ContentResolver 的请求目标时启动

清单

应用清单文件
用来确认组件存在,声明组件
AndroidManifast.xml,是核心配置文件
包含一个应用的所有组件的声明

  • 声明组件
    <activity> ___Activity
    <service> ____服务
    <receiver> ___广播接收器
    <provider> ___内容提供程序
    源代码中有,但是未在清单中声明的组件,系统看不见
    不过,广播接收器可以在清单文件中声明或在代码中动态创建
    (如 BroadcastReceiver 对象) 并通过调用 registerReceiver() 在系统中注册

  • 声明组件功能
    Intent 的真正强大之处 在于隐式 Intent 概念
    作用: 描述要执行的 操作 类型 (可选择描述要执行的操作所针对的数据类型),
    让系统 能够 在设备上找到可执行该操作的组件,并启动该组件
    若有多个组件可以执行 Intent 所描述的操作,则由用户选择使用哪一个组件
    系统如何找?
    系统通过将接收到的 Intent 与设备上的 其他应用的清单文件中 提供的 Intent 过滤器 进行比较来确定可以响应 Intent 的组件
    为组件声明 Intent 过滤器
    <intent-filter> 元素作为组件声明元素的子项进行添加 (意图接收器)

  • 声明应用要求
    在清单中声明,为应用支持的设备类型明确定义一个配置文件
    android:required = "true" ,声明应用要求,例如:没有相机不能安装
    若不要求,则改为 false
    建议API级别 19,对应Android版本 4.4

<manifest ... >
    <uses-feature android:name="android.hardware.camera.any"
                  android:required="true" />
    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
    ...
</manifest>

R类

  • Android 项目中包括的每一项资源,SDK 构建工具都会定义一个唯一的整型 ID
    R.java 生成一个系统常量,R.目录.文件名,即资源 ID
    这里的 R类 是系统自动生成的,程序员 不能修改 此类中的内容

  • 限定符
    是一种加入到资源目录名称中,用来定义这些 资源适用的设备配置 的简短字符串

开发者模式

在搭载 Android 4.2 及更高版本的设备上,“开发者选项”默认情况下处于隐藏状态
开发者模式 > 打开USB调试 > USB连接方式选择 传输文件 >
打开Android Studio > 选择Troubleshoot Devices Connections

可以通过 WLAN 连接到设备,不过用USB比用WLAN方便一些

  • Android 调试桥 (Android Debug Bridge) 是一种功能多样的命令行工具
    客户端,发送adb命令给服务器
    守护进程 (adbd),接收abd命令的转接,在设备 (手机) 上运行命令
    服务器,接收abd命令,确定手机上有守护进程,发给手机

日志

  • Logcat,日志,View > Tool Windows > Logcat
    Log level:Verbose (所有日志),Debug,Info,Warn,Error,Assert (级别递增)
    写日志Log.v(String, String),两个参数分别为 项名(键),值 20.3.2
    可通过 过滤器 限制日志的输出

链接 Android Basic_Activity Empty_Activity Bottom_Navigation_Activity Fragment + ViewModel etc.
链接 Android 概述_问答.
链接 Android 概述_习题.

Android UI 开发


  • UI 就是用户界面

ViewGroup 和 View

  • ViewGroup 继承自 View
    View 对象是一块矩形区域,用于在屏幕上绘制可供用户交互的内容
    ViewGroup 对象是一种不可见的容器

布局声明

  • 在 XML 中声明 UI 的优点
    将应用的 外观 与控制应用行为的 代码 分离
    UI 描述位于应用代码外部,在修改或调整外观的描述时,无需修改源代码

加载 XML 资源
启动 Activity 时,Android 框架调用其 onCreate 方法加载布局资源
on开头,通常都是回调方法 (当系统中发生某种事件时,由系统调用的方法)

  • 每个布局文件都必有且只能有一个根元素 (可为 View 或 ViewGroup对象)
    小部件 (微件 widget) 中不能再嵌入其他元素

  • ID 用于在结构树中对 View 对象进行唯一标识
    XML 标记内部的 ID 语法

android:id="@+id/my_button"		// + 号表示创建该名称并将其添加到资源内 (R.java中)

android:id="@android:id/empty"	//引用 Android 资源 ID
  • 创建视图并从应用中引用
//在布局文件中定义一个视图/小部件,并为其分配一个唯一的 ID:
<Button android:id="@+id/my_button"/>
        
//然后创建一个 view 对象实例,并从布局中捕获它(通常在 onCreate() 方法中):
Button myButton = (Button) findViewById(R.id.my_button);

在 Java 代码中通过 findViewById()方法,获取该 View 的引用,并强制转型为 Button 类型,此后,代码中就可以使用 myButton 来引用该按钮了

布局参数

  • 所有视图组都包括宽度和高度 (layout_width 和 layout_height),并且每个视图都必须定义它们
    wrap_content 同内容一样的尺寸
    match_parent 同父视图几近一样的尺寸

布局位置

  • 每个 view 的形状为一个矩形,left 和 top 表示它的位置,width 和 height 表示它的大小
    下图 利用各种方法获取 View 坐标
    在这里插入图片描述

度量单位

显示度量单位

  • 长度单位
    in,英寸 (inch),每英寸 2.54 厘米,屏幕物理尺寸
    pt,表示一个点 (Points),1 / 72 英寸,在 72dpi 屏幕上 1pt = 1px,屏幕物理尺寸
  • 像素单位
    px,对应屏幕上实际像素点
    dip = dp,密度无关的像素,一种基于屏幕密度的抽象单位
    sp,和dp的概念相似,用于字体大小的单位
  • 下面六种密度集
    ldpi (low) ~120dpi
    mdpi (medium) ~160dpi
    hdpi (high) ~240dpi
    xhdpi (extra-high) ~320dpi
    xxhdpi (extra-extra-high) ~480dpi
    xxxhdpi (extra-extra-extra-high) ~640dpi
    若把 mdpi 作为基准密度,其他密度的实际像素px = dp * (dpi / 160)

视图 View 的尺寸通过宽度和高度表示
测量宽度和测量高度,视图想要在其父项内具有的大小
绘制宽度和绘制高度,简称宽度高度,视图在绘制时和布局后在屏幕上的实际尺寸
因为程序代码中可能在真正显示布局的时候,
修改了它的大小,最终使得其显示大小和定义大小不一致

常见布局

常见布局
布局的嵌套布局越少,绘制速度越快(扁平的视图层次结构优于深层的视图层次结构)
布局用 LinearLayout (线性布局)足够,约束布局 (ConstraintLayout) 比较高级比较难

  • 相对布局,RelativeLayout
    每个 view 的位置通过相对它的兄弟 view 或父 view 来进行定位
  • 线性布局,LinearLayout
    使用单个水平行或垂直行来组织子项的布局
  • 表格布局,TableLayout 是 LinearLayout 的子类
    行由 TableRow 表示,表列的个数由包含最多子元素的 TableRow 所决定
  • 网格布局,GridLayout 在 Android4.0之后
    用一组没有宽度的线将屏幕区域划分为纵横交错的格子,将子控件依次放在格子 (cell) 里
    网格线用下标表示
  • 帧布局,FrameLayout
    为每个加入其中的控件创建一个空白区域,只在左上角显示
  • 绝对布局,AbsoluteLayout (不推荐使用)
    需要通过指定 layout_x、layout_y 坐标来控制每一个控件的位置

视图可以定义内边距 (Padding),但不支持外边距 (Margin)
在表示视图组中的视图之间时,可用 margin,别的不可
width 设为 0dp 后,再改 weight 权重,比例关系 (因为是 剩余空间 按权重比例分配)
gravity (重力) 指内容
layout_gravity 指相对父控件的位置
下去自己学 20.3.9

链接 Android UI 开发_问答_1.
链接 Android UI 开发_问答_2.
链接 Android UI 开发_问答_3.
链接 【Android】Android UI 开发_习题.

Activity


链接 【Android】Activity.(占篇幅较大)
链接 【Android】Activity_问答.
链接 【Android】Activity_习题.

数据存储


Internal 内部存储区 只能app访问
External 外部存储区 世界可读,不具有保密性
卸载app时,Internal 中的都删,External public 的文件保留 private 的文件删除
app默认安装到 Internal 存储区

  • 内部存储

获取合适的目录作为File对象
getFilesDir() 为你的app返回一个代表internal目录的File对象
getDir(name,mode) 你的app的内部存储目录中创建或者打开一个目录
getCacheDir() 返回一个用于存放你的app 临时 缓存文件的internal目录

  • 写入文件 (注 openFileOutput 方法默认打开内部存储区目录)
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;

try {
  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
  outputStream.write(string.getBytes());	//转换为字节流
  outputStream.close();
} catch (Exception e) {
  e.printStackTrace();
}
  • 写一个缓存文件
public File getTempFile(Context context, String url) {
    File file;
    try {
        String fileName = Uri.parse(url).getLastPathSegment();
        //createTempFile(String prefix, String suffix, File directory)
        //在指定目录中创建空文件, 用给定的前缀名prefix和后缀名suffix
        file = File.createTempFile(fileName, null, context.getCacheDir());
    } catch (IOException e) {
        // Error while creating file
    }
    return file;
}

fileList()方法获取你的app的所有文件名的字符数组

保存静态文件
在项目的 res/raw/ 目录中保存该文件
可以使用 openRawResource() 打开该资源并传递 R.raw.<filename> 资源 ID
创建 raw :res右击 > New > Android Resource Directory

  • 外部存储

  • 申请权限 获取外部存储的访问权限
    写权限本身是包含读权限的

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>
  • 测试状态 校验外部存储是否可用
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}	//是否可写

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}	//是否可读

getExternalStoragePublicDirectory() 获取外部存储的公共区域的目录
getExternalFilesDir() 获取外部存储的私有区域的目录

  • XML序列化 模板
1.设置文件编码方式:serializer.setOutput(fos,"utf-8");//fos为文件输出流对象

2.写入xml文件的开始标签:serializer.startDocument("utf-8",true); 第一个参数设置文档的编码格式,第二个参数设置是否是一个独立的文档,一般设置为true

3.依次写入各元素(如果有多个元素则可以使用迭代的方式写入,如果标签是嵌套的,则在写入顺序上也是嵌套的):
a) 写入开始标签:serializer.startTag(null,"Persons"); 这里第一个参数为xml的命名空间,没有可以用null,第二个参数为标签名
b) 如果该标签有属性:serializer.attribute(null,"id",1);其中第一个参数为命名空间,第二个参数是属性名,第三个参数为属性值
c) 写入元素内容:serializer.text(person.getName());该参数为实例对象中的某个属性值
d) 写入结束标签:serializer.endTag(null,"Persons");第一个参数为命名空间,一般为null即可,第二个参数为结束标签的标签名

4.以这个语句表示文档的写入结束:serializer.endDocument()

5.通过serializer.flush()将流写入文件中
最后,关闭输出流:fos.close()

XML解析

  • DOM解析 只能解析较小的文件
  • SAX解析 逐行扫描 可以解析超大的
  • PULL解析 和SAX差不多

SQLite,是一款轻型的数据库
设计目标 嵌入式设备 用的资源比较少
点命令中的命令名同点之间没有空白符
点命令必须在同一行
点命令不能在普通SQL语句中
点命令不接受注释

.open 数据库名称,如果该数据库在磁盘中不存在,则创建并打开,如果存在,则直接打开
.save 数据库名称,如果sqlite3的启动是通过双击windows中的sqlite3.exe的图标打开的,系统会在内存中创建一个数据库,这个数据库如果需要存放到磁盘,则需要使用本命令进行存放。注意如果磁盘上如有同名的数据库,会覆盖。
.databases,用于列出数据库
.tables,用于列出数据库中的数据表

控制台中输入:chcp 65001,把控制台的字符编码由GBK切换为utf-8
contract类 协约类 伙伴类
定义表名和单个表的列名

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // give it an empty constructor.
    public FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static abstract class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_ENTRY_ID = "entryid";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
        ...
    }
}

创建和删除表的语句

private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" +
    FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
    FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
    ... // Any other options for the CREATE command
    " )";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + FeedReaderContract.FeedEntry.TABLE_NAME;

少用 execSQL,因SQL注入

链接 【Android】数据存储.
链接 数据存储_问答.
链接 数据存储_习题.

UI进阶


使用 AdapterView 的三个步骤

  • 1.设置数据源
  • 2.创建 Adapter,并将数据源和 Adapter 绑定在一起
  • 3.将装载好的 Adapter 绑定到我们想要展示的控件上面

AdapterView 的两个子类,ListView、GridView

ListView 是一个可滚动的条目列表,是一个 ViewGroup
通过 Adapter 将数据从数据源自动加载到表中
常用方法 setAdapter 将控件和适配器联系起来

public void setAdapter(ListAdapter adapter)

ListAdapter 是 Adapter 的一个子接口

主要方法
abstract View getView(int position, View convertView, ViewGroup parent)
参数
position 显示数据项的位置,若数据集是数组,该参数即为下标
convertView 被复用的老的 view,若为 null,则回收器中没有,需新建
这是防止数据项(表项)过多 设计者对控件进行复用,调用 setTag getTag 指定 ViewHolder 对应的控件
//滚出页面的数据条目,放在回收器里
parent 绑定的父 view

Adapter 的实现子类 主要:BaseAdapter ArrayAdapter SimpleAdapter

  • BaseAdapter 抽象类
    继承时需要覆写 getView 例如
public View getView(int position, View convertView, ViewGroup parent) {
    //ViewHolder用于保存对子view的引用
    ViewHolder holder;
    if (convertView == null) {
        convertView = View.inflate(MainActivity.this,R.layout.list_item, null);		//新建
        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.tv_list);
        holder.icon = (ImageView) convertView.findViewById(R.id.image);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();		//若非空 取出来 设置相应的值
    }
//通过holder来 绑定数据
    holder.text.setText(names[position]);
    holder.icon.setImageResource(icons[position]);
    return convertView;
}
static class ViewHolder {	//内嵌亦可
    TextView text;
    ImageView icon;		//此处一个表项里包含一个文本和一个图片
}
  • ArrayAdapter
    继承自 BaseAdapter,用于显示数据源为任意对象的数组

构造方法

  • 1.public ArrayAdapter (Context context, int resource)
    resource:布局文件的 ID,有且只能有一个 TextView (R.layout.)
    也可用系统给的android.R.layout.simple_expandable_list_item_1
  • 2.public ArrayAdapter (Context context, int resource, int textViewResourceId)
    resource 限制解除
    textViewResourceId:把布局文件中的 TextView id 写上即可 (R.id.)
  • 3.public ArrayAdapter (Context context, int resource, T[] objects)
    比之1,不用 add,直接在第三个参数放入数组
  • 4.public ArrayAdapter (Context context, int resource, int textViewResourceId, T[] objects)
    2 + 3,结合一下

后面就是用表,不说了

  • SimpleAdapter
    用来将静态数据显示在XML文件所定义的视图中

构造方法

public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
  • from 对应 to,to 数组包含的是布局的所有控件 id,from 数组包含的是 data 取值对应的字符串
public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.activity_main,
                new String[]{"img","title"},
                new int[]{R.id.iv_img,R.id.tv_title});

        setListAdapter(adapter);
    }

    private List<Map<String,Object>> getData(){

        List<Map<String,Object>> list = new ArrayList<>();

        Map<String,Object> map = new HashMap<>();
        map.put("img",R.drawable.jd);
        map.put("title","京东商城");
        map.put("info", "no.1");	//因 from 数组中不包含 info,故 no.1 不会被写入,也无处可写
        list.add(map);

        return list;
    }
}

Fragment 分段

Fragment 类和 Activity 类很像,它包含的回调方法同 activity 相似
其中 Stoppd 状态:移除并且在 back 栈中时

f 与 a 生命周期显著不同在于:

  • activity 停止时,被系统自动放入 系统管理的 back 栈
  • Fragment 停止时,放入宿主 activity 所管理的 back 栈中,
    而且是在事务中移除 Fragment 时,调用 addToBackStack 显式放入的

别的都类似,而且 Activity 的生命周期直接影响 Fragment 的,
只有 Activity 在 Resumed 状态时,Fragment 的生命周期能独立变化
多的回调方法

  • onAttach,Fragment 和 Activity 关联时调用
  • onCreateView,创建同 Fragment 关联的视图层次时调用
  • onDestoryView,视图层次被移除的时候
  • onDetach,解除关联的时候

引入布局,需要 LayoutInflater

public class ArticleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 把布局应用到fragment
        return inflater.inflate(R.layout.article_view, container, false);	//false 未绑定 ViewGroup
    }
}

可以把一个 Fragment 当作一个 view 一样
1.静态,布局文件
Fragment 三种给 id 的方法

  • 1.id 属性
  • 2.tag 属性
  • 3.用其容器 view 的 id

2.编程加入 viewGroup

FragmentManager fragmentManager = getFragmentManager()		//管理 Fragment
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();	//开始一个事务 

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);		//对 Fragment 进行 添加 移除 替换等都可
fragmentTransaction.commit();		//记得提交

添加没有 UI 的 Fragment,用来为 activity 提供后台行为
不用实现 onCreateView,没有 id 属性,只能用 tag

放入 back 栈是人工调用 addToBackStack 方法,不会自动放

Fragment 通过 getActivity() 获取 activity 的实例

View listView = getActivity().findViewById(R.id.list);	//在 Fragment 调用 getActivity 时,必须已绑定一起,否则返回 null

activity 通过 FragmentManager 中的findFragmentById/Tag 方法来获取 Fragment 的引用

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

Fragment 之间传递信息需要借助 其宿主 Activity

public static class FragmentA extends ListFragment {
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    
    //判断 宿主 activity 是否实现接口
    public void onAttach(Activity activity) {	// Fragment 和 Activity 关联时调用
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    
    public void onListItemClick(ListView l, View v, int position, long id) {	//id 点击项
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);	//调用该方法
    }
}

内容提供者


ContentProvider 四大组件之一,管理对结构化数据集的访问

  • 是一个进程同另一个进程连接数据的标准接口
    应用程序之间共享数据的手段之一
  • 以 URI 的形式对外提供数据存取途径
  • 增删改查

ContentResolver,作为客户端,自动与 Provider 暴露的数据进行进程间通信 (可以有多个)

  • ContentProvider 封装数据,并通过 ContentResolver 向其他应用提供数据
  • 通过 Uri 的 authority 找到对应的 ContentProvider

URI 统一资源标识符(Uniform Resource Identifier),标识某一资源名称的字符串

  • URI = scheme + authority + path
    authority = host + port
    path 分层,称片段
  • 给定字符串转换为 URI 用 parse 方法

UriMatcher 用于匹配content provider中的Uri,建立 URI 树

public static final int NO_MATCH	//值 -1
public UriMatcher (int code)	//参数值为 NO_MATCH  创建 URI 树根节点
public void addURI (String authority, String path, int code)	//添加 uri 到树中,同一个树中 code 不重复

int match = sURIMatcher.match(uri);		//	取其 code 值

ContentUris 关于Content uri的工具类
语法 content://authority/path/id

  • 可用 parseId 方法取其 id 值
  • 用 withAppendedId (Uri contentUri, long id) 方法 在uir 后追加 id

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型
ContentProvider 有两个方法返回 MIME 类型

  • getType() 返回一个MIME格式的字符串
    你必须实现的一个方法
    若某个 provider 的 authority 是 com.example.app.provider,
    且有一个表的名字为 table1,table1 的多行MIME类型是
vnd.android.cursor.dir/vnd.com.example.provider.table1
单行就是将 dir 改为 item
  • getStreamTypes() 返回一个MIME类型的字符串数组
    如果你的 provider 提供文件的话,你需要实现的方法

关于访问权限
默认情况下,无权限,都可访问

  • 设置权限:<provider> 元素的 android:permission 属性 假设A
    为了让权限只针对你的provider,使用android:name属性 假设B
  • 访问者 需 <permission> 元素的 name 属性与 A 匹配
    <uses-permission> 元素的 name 属性与 B 匹配

Message 类
常用域

  • public int arg1,arg2 传两个 int,多了只能用 setData
    替代 setData 方法的低成本方式
  • public Object obj 传一个 obj,多了同上
  • public int what 消息代码,类似于 requestCode (隐式意图里的)
    每个 Handler 都有自己的消息代码的命名空间,无需担心冲突

常用方法

  • setData
  • getData
  • obtain
    public static Message obtain (Handler h, int what, int arg1, int arg2, Object obj)
    无参数是传给默认的
    参数为 Message orig 复制参数消息,消息复制
  • setTarget
    然后 sendToTarget

Looper
常用方法

  • loop 启动线程中的消息队列
  • prepare 将当前线程转换为 looper 线程
class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
      Looper.prepare();

      mHandler = new Handler() {	//新建的 Handler 和所在的线程的 looper 及消息队列相关联 此过程在构造方法中完成
          public void handleMessage(Message msg) {
              // process incoming messages here
          }
      };

      Looper.loop();
    }
}

Handler
Handler 使得你可以发送和处理线程的消息队列中的消息
每个handler对象同某个线程及它的消息队列相关

  • 一个线程可有多个,若和消息对象则只能有一个
  • handler 同创建其的线程关联,在哪个线程创建,就和哪个线程的 looper 相关联
  • 可向消息对象发送 消息 和 Runable 对象 (也是个线程对象)

主要用途
1.安排消息 并且在未来的某个时候执行某个 Runable 对象
post 发送的是 Runable 对象,send 发送的才是 Message
2.排队 在其他线程要执行的动作

构造方法

  • Handler(),无参,关联当前
  • Handler(Looper looper),指定关联的 looper

常用方法

  • void handleMessage(Message msg),必须覆写

广播接收者


BroadcastReceiver 组件,用于监听并处理系统中的广播
需要创建并进行注册 (组件都需要注册,在清单文件中注册)
注册的两种方式

  • 静态注册 <receiver>,常驻型广播接收者
<receiver android:name = ".MyBroadcastReceiver">	//java 文件
   <intent-filter android:priority = "1000">		//优先级 -1000 ~ 1000 默认为0
       <action android:name = "android.provider.Telephony.SMS_RECEIVED" />	//接收广播的类型
   </intent-filter >
</receiver >
  • 动态注册 Context.registerReceiver(),非常驻广播接收者,依赖其所在的组件
    若组件被销毁,该广播也不再启动
1.定义广播接收者
private BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
       // 相关处理,如收短信,监听电量变化信息
    }
};

2.创建IntentFilter对象
IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED " );	//指定接收类型
优先级通过 setPriority 方法设置

3.注册
registerReceiver( mBatteryInfoReceiver , intentFilter);

注册建议放在 onResume 回调方法中
注销建议放在 onPause 中

生命周期
广播接收者对象只在 onReceiver 方法被调用期间有效,方法结束就销毁,所以不能执行异步
所以勿考虑比较耗时的操作,耗时的可用 service

广播的类型

  • 普通,通过 Context.sendBroadcast (intent, receiverPermission) 进行广播,异步,
    接收者以无序方式同时接收,处理结果不能传递,广播不能被截断
    有时系统会让接收者一个一个来,但还是普通广播
    参数
    intent,广播通过意图发送,载体是意图
    receiverPermission,指定权限,可省
  • 有序,通过 Context.sendOrderBroadcast (intent, string) 广播,接收者轮流接收,处理结果可传给下一个
    优先级高的先接收
    public abstract void sendOrderedBroadcast (Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)
    BroadcastReceiver ,不管是否中断,指定最终接收者
  • 注,广播用的 intent 与 activity 用的 intent 机制不同

判断广播是否有序 isOrderedBroadcast

有序广播常用方法

  • 中断 abortBroadcast
  • 取消中断 clearAbortBroadcast
  • 获取结果状态和数据
    getResultCode getResultData getResultExtras 结果码,结果数据,附加数据
  • 设置结果状态和数据
    总 public final void setResult (int code, String data, Bundle extras)
    setResultCode setResultData setResultExtras 与上对应

服务


服务的两种形式

  • 1.启动 其它组件调用 startService,启动后无限期运行 需实现 onStartCommand onBind
  • 2.绑定 其它组件调用 bindService,所有绑定取消,服务销毁 需实现 onBind

密集型建议另开单独线程
可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互

onBind 必须实现,不允许绑定返回 null

service 必须在 清单 中声明
只能通过显示 intent 来启动,所以发布后最好不要改 name

<service android:name=".ExampleService" />

android:exported //false 则服务只能用于你的 app

启动服务
调用 startService() 方法并传递 Intent 对象
服务通过 onStartCommand() 方法接收此 Intent

Intent intent = new Intent(this, HelloService.class);
startService(intent);		//后面系统调用各种方法

intent 是组件和服务之间的唯一通信模式

停止服务:服务调用 stopSelf,组件调用 stopService 方法
只要调用 onStartCommand,就是启动服务,不会自动销毁

继承类

  • Service
    默认使用 主线程,尽量创建一个用于执行所有服务工作的新线程
  • IntentService
    逐一处理所有启动请求,需实现 onHandleIntent

IntentService

创建一个 worker 线程来执行提交到 onStartCommand() 方法的意图,该线程区别于你的 app 的主线程
创建一个工作队列,每次向 onHandleIntent() 传递一个 intent,所以你从来不必担心多线程
当所有的请求处理完毕以后,你从来不必调用 stopSelf()
提供一个返回 null 的 onBind() 的实现
提供一个 onStartCommand() 的默认实现,该实现把intent提交到工作队列及你的 onHandleIntent() 实现

通过广播的形式显示结果

Service
实现代码示例

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.  We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);		//发送给线程

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

START_NOT_STICKY
不会重建,除非有挂起的 intent 需处理

START_STICK
服务重新启动的时候无需提交 intent,除非有挂起的 intent 需处理

START_REDELIVER_INTENT
再次提交 intent

绑定服务
生成 IBinder 接口
解绑 unbindService

实现 onBind
多个client连接到service,系统只执行第一个 client 的 onBind() 方法,返回 IBinder 对象
绑定调用

public abstract boolean bindService (Intent service, ServiceConnection conn, int flags)
两个方法
public abstract void onServiceConnected (ComponentName name, IBinder service)
public abstract void onServiceDisconnected (ComponentName name)

定义 IBinder 接口的方法:

  • 继承Binder类,同一个进程
  • Messenger,不同进程

cast 类型转换

public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();		//nothing

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);	//只是一个例子
    }
}

下面的 Activity 绑定上面的 LocalService 并调用其中方法

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值