《Android开发艺术探索》
一、Activity 的生命周期和启动模式
1. 当前 Activity 为 A,此时打开 Activity B:A.onPause() → B.onCreate() → B.onStart() → B.onResume() → A.onStop(),故不能在 onPause 中做重量级操作,使新 Activity 尽快显示出来并切换到前台。
2. 当系统内存不足时,系统会按照 [ 后台 Activity → 可见但非前台 Activity → 前台 Activity ] 的优先级杀死目标 Activity 所在的进程。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死 → 故 将后台工作放入 Service 中从而保证进程有一定的优先级,不易被轻易杀死。
(1) standard 标准模式(系统默认)—— 多实例实现
每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在,被创建的实例的生命周期符合典型情况下 Activity 的生命周期。
一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。
谁启动了这个 Activity ,这个 Activity 就运行在启动它的那个 Activity 所在的栈中。
当用 ApplicationContext 启动 standard 模式的 Activity 时会报错,因为非 Activity 类型的 Context 并没有所谓的任务栈,解决:为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候会为它创建一个新的任务栈,此时待启动 Activity 实际是以 singleTask 模式启动的。
(2)singleTop 栈顶复用模式
若新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的 onNewIntent 方法会被回调,通过此方法的参数可以取出当前请求的信息。此 Activity 的 onCreate、onStart 不会被调用,因为它并没有发生改变。若不在栈顶,则会重新创建。
(3)singleTask 栈内复用模式 —— 一种单实例模式
只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例。也会回调 onNewIntent。
如 Activity A 为 singleTask 模式启动后,系统首先寻找是否存在 A 想要的任务栈,若不存在,则重新创建一个任务栈,然后创建 A 的实例放入栈中。若存在 A 所需的任务栈,若栈中有 A 的实例存在,系统就把 A 调到栈顶并调用 onNewIntent 方法,同时由于singleTask 默认具有 clearTop 效果,会导致栈内所有在 A 上面的 Activity 全部出栈;若实例不存在,则创建并压入栈中。
(4)singleInstance 单实例模式 —— 加强的 singleTask
具有此种模式的 Activity 只能单独地位于一个任务栈中。
4. Activity 所需的任务栈
参数 TaskAffinity 标识了 Activity 所需的任务栈的名字,其值为字符串,且中间必须含有包名分隔符“.”。默认所有 Activity 所需的任务栈名字为应用的包名。也可为每个 Activity 单独指定 TaskAffinity 属性,但其值不能和包名相同。该属性主要和 singleTask 启动模式或 allowTaskReparenting 属性配对使用。
任务栈分为前台任务栈和后台任务栈,后台任务栈中的 Activity 位于暂停状态。
(1)TaskAffinity 和 singleTask 结合使用:TaskAffinity 是该 Activity 的目前任务栈的名字,待启动的 Activity 会运行在名字和 TaskAffinity 相同的任务栈中。
(2)TaskAffinity 和 allowTaskReparenting 结合使用:当应用 A 启动了应用 B 的某个 Activity 后,若这个 Activity 的 allowTaskReparenting 属性为 true,则当 B 被启动后,此 Activity 会直接从 A 的任务栈转移到 B 的任务栈中。
5. IntentFilter 匹配规则
(1)action
要求 Intent 中必须有一个 action,且必须能够和过滤规则中的某个 action 相同
(2)category
Intent 中可以没有 category,但一旦有 category,则不管有几个必须每个都要能和过滤规则中任何一个 category 相同。
为了 Activity 能够接收隐式调用,必须在 intent-filter 中指定 “android.intent.category.DEFAULT”。
(3)data
语法:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;"><data android:scheme="string" //URI 的模式,如 http、file、content。若未指定 scheme,则整个 URI 无效。默认为 content 和 file。
android:host="string" //主机名,如 www.baidu.com。若未指定,则 URI 无效。
android:port="string" //端口号,仅当 URI 中指定了 scheme 和 host 时才有效。
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string"/></span></span></span>
data 由 2 部分组成,mimeType (媒体类型)和 URI。
URI 结构:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;"><scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]</span></span></span>
Intent 中必须含有 data,且 data 能完全匹配过滤规则中的某一个 data。
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">android:pathPattern="string"</span></span></span>
注:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;"><action android:name="android.intent.action.MAIN/>
<category android:name="android.intent.category.LAUNCHER"/></span></span></span>
二、IPC 机制
1. IPC:Inter-Process Communication,进程间通信 / 跨进程通信。
2. 主线程也叫 UI 线程,在 UI 线程里才能操作界面元素。
3. Android 中的多进程模式
在 Android 中使用多进程只有一种方法:给四大组件在 AndroidMenifest 指定
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">android:process //若未指定,默认进程名是包名</span></span></span>
属性,即我们无法给一个线程或一个实体类指定其运行时所在的进程。
进程名以 “:” 开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中;
进程名不以 ":" 开头的属于全局进程,其他应用通过 ShareUID 方式可以和它跑在同一个进程中。
Android 系统会为每一个应用分配一个唯一的 UID,具有相同 UID 的应用才能共享数据。
只有 2 个应用有相同的 ShareUID 且签名相同才可以跑在同一个进程中。此时不管它们是否在同一个进程中都可以互相访问对方的私有数据,如 data 目录、组件信息等;若在同一个进程中,则还可以共享内存数据。
Android 为每个进程都分配一个独立的虚拟机(相当于把应用重启了一遍),不同的虚拟机在内存分配上有不同的地址空间,导致在不同的虚拟机中访问同一个类对象会产生多份副本。
所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。
使用多进程有如下问题:
(1)静态成员和单例模式完全失效。
(2)线程同步机制完全失效。
(3)SharedPreferences 的可靠性下降。(不支持 2 个进程同时写)
(4)Application 会多次创建。
4. 序列化
(1)Serializable 接口
是 Java 提供的序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。使用简单,但开销大,需要大量I/O操作。
使用 Serializable 实现序列化只需这个类实现 Serializable 接口并声明如下标识即可自动实现默认的序列化过程:
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">private static final long serialVersionUID = 87254421243431L;</span></span></span>
若不声明会对反序列化产生影响。
注:静态成员变量属于类不属于对象,故不会参与序列化过程;用 transient 关键字标记的成员变量不参与序列化过程。
(2)Parcelable 接口
Android 提供的序列化接口。使用稍麻烦,但效率高。
Android 序列化首选 Parcelable,主要用在内存序列化上;将对象序列化到存储设备或通过网络传输应使用 Serializable。
5. Android 中的 IPC 方式
(1)Bundle
传输的数据必须能被序列化,如 基本类型、实现了 Parcelable 或 Serializable 接口的对象以及一些 Android 支持的特殊对象。
(2)文件共享
对文件格式没有要求,只要读 / 写双方约定数据格式即可。
注:SharedPreferences 是 Android 中提供的轻量级存储方案,也是文件的一种,但系统对它的读 / 写有一定的缓存策略,即在内存中会有一份 SharedPreferences 文件的缓存,在多进程模式下,系统对它的读 / 写就变得不靠谱,当面对高并发的读 / 写访问时会有很大几率丢失数据。故不建议在进程间通信中使用 SharedPreferences。
(3)Messenger
一种轻量级的 IPC 方案,底层实现是 AIDL。
一次处理一个请求,故在服务端不用考虑线程同步的问题,因为服务端中不存在并发执行的问题。
串行方式,不适合大量并发请求。
(4)AIDL
(5)ContentProvider
Android 提供的专门用于不同应用间进行数据共享的方式。底层实现是 Binder。
主要以表格的形式组织数据,还支持文件数据。
需要注册,
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">android:authorities //ContentProvider 的唯一标识。</span></span></span>
(6)Socket 套接字
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;"><uses-permission android:name="android.permission.INTERNER"/>
<uses-permission android:name="android.permission.ACCESS_NEWWORD_STATE"/></span></span></span>
不能在主线程中访问网络
名称 | 优点 | 缺点 | 适用场景 |
Bundle | 简单易用 | 只能传输 Bundle 支持的数据 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,无法做到进程间的即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有 RPC 需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好处理高并发情形,不支持 RPC,数据通过 Message 进行传输,只能传输 Bundle 支持的数据类型 | 低并发的一对多即时通信,无 RPC 需求,或者无须返回结果的 RPC 需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作 | 可理解为受约束的 AIDL,主要提供数据源的 CRUD 操作 | 一对多的进程间的数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍有繁琐,不支持直接的 RPC | 网络数据交换 |
三、View 的事件体系
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">x = left + tanslationX;
y = top + translationY;</span></span></span>
- ACTION_DOWN
- ACTON_MOVE
- ACTION_UP
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">getX / getY:相对于当前 View 左上角的 x 和 y 坐标;
getRawX / getRawY:相对于手机屏幕左上角的 x 和 y;</span></span></span>
② TouchSlop
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">ViewConfiguration.get(getContext()).getScaledTouchSlop();</span></span></span>
(3)
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);</span></span></span>
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">velocityTracker.computeCurrentVelocity(1000);//计算速度:在一段时间内手指所划过的像素数,如 1000ms
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();</span></span></span>
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">velocityTracker.clear();
velocityTracker.recycle();</span></span></span>
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">GestureDetector mGestureDetector = new GestureDetector(this);
//解决长按屏幕后无法拖动的现象
mGestureDetector.setIsLongpressEnabled(false);</span></span></span>
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">boolean consume = mGestureDetector.onTouchEvent(event);
return consume;</span></span></span>
完成上述 2 步后就可以有选择地实现 OnGestureListener 和 OnDoubleTapListener 中的方法了。
2. View 的滑动
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">mScrollX = View 左边缘 - View 内容左边缘;
mScrollY = View 上边缘 - View 内容上边缘;</span></span></span>
即从右向左滑动 mScrollX 为正,从下往上滑动 mScrollY 为正。
View 动画(xml)是对 View 的影像做操作,它并不能真正改变 View 的位置参数,包括宽 / 高,故会影响 View 的单击等交互事件。若希望动画后的位置状态得以保留还必须将 fillAfter 属性设置为 true。
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">MarginLayoutParams params = (MarginLayoutParams) btn.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
btn.requestLayout();
//或 btn.setLayoutParams(params);</span></span></span>
②
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}</span></span></span>
点击事件优先级:
(10)onClick 会发生的前提是当前 View 是可点击的,且收到了 down 和 up 事件。
<span style="font-size:14px;"><span style="font-size:14px;">public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false; //父容器必须返回 false,即不拦截 ACTION_DOWN 事件,否则后续的 MOVE 和 UP 都会直接由父容器处理,而不会传递给子元素了。
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false; //UP 事件没多大意义,必须返回 false。
break;
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}</span></span>
(2)内部拦截法
<span style="font-size:14px;"><span style="font-size:14px;">public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要当前点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}</span></span>
<span style="font-size:14px;"><span style="font-size:14px;">public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}</span></span>
四、View 的工作原理
- measure:测量 View 的宽和高,Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 获取 View 测量后的宽 / 高,在几乎所有的情况下它都等同于 View 的最终的宽 / 高。特殊情况除外。
- layout:确定 View 在父容器中的放置位置,即 View 的四个顶点的坐标和实际的 View 宽 / 高,完成后可以通过 getTop、getBottom、getLeft、getRight获取四个顶点的位置,getWidth 和 getHeight 获取最终宽 / 高。
- draw:负责将 View 绘制在屏幕上,只有 draw 完成后 View 的内容才能显示在屏幕上。
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;">ViewGroup content = (ViewGroup) findViewById(android.id.content); //获取 content
content.getChildAt(0); //获取我们设置的 View</span></span></span>
2. MeasureSpec
- UNSPECIFIED
- EXACTLY
- AT_MOST
- LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小。
- LayoutParams.WRAP_CONTENT:最大模式,大小不定,但不能超过窗口的大小。
- 固定大小:精确模式,大小为 LayoutParams 中指定的大小。
关于 getSuggestedMinimumWidth() 方法:若 View 没有设置背景,那么返回 android:minWidth 的值(可以为 0);若 View 设置了背景,则返回 android:minWidth 和 背景的最小宽度 两者中的最大值。getSuggestedMinimumWidth 的返回值就是 View 在 UNSPECIFIED 情况下的测量宽。
<span style="font-size:14px;"><span style="font-size:14px;">/**
* 只需给 View 指定一个默认的内部 宽/高(mWidth 和 mHeight),并在 wrap_content 时设置此宽/高即可。
* 对于非 wrap_content 情形,沿用系统的测量值即可。
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}</span></span>
<span style="font-size:14px;">public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}</span>
②view.post(runnable)。
<span style="font-size:14px;">protected void onStart() {
super.onStart();
view.post(new Runnable() {
@override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}</span>
<span style="font-size:14px;">protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}</span>
(3)draw 过程
- 继承 View 重写 onDraw 方法
- 继承 ViewGroup 派生特殊的 Layout
- 继承特定的 View (如 TextView)
- 继承特定的 ViewGroup(如 LinearLayout)
②若有必要,让 View 支持 wrap_content
《Android 应用程序开发权威指南》
一、基础
-
管理器 功能 LocationManager 和设备上的基于位置的服务进行交互 ViewManager,WindowManager 负责显示界面以及设备相关的用户界面的基础 AccessibilityManager 负责辅助事件,提供对物理损伤的用户的设备支持 ClipboardManager 提供了访问设备全局剪贴板的能力,可以剪切和复制内容 DownloadManager 作为系统服务,负责 HTTP 的后台下载 FragmentManager 管理一个 Activity 的 Fragment AudioManager 提供了音频和振铃控制的访问 -
文件 功能 AndroidManifest.xml 应用的核心配置文件。定义了应用程序的功能和权限,以及如何运行。 ic_launcher-web.png 一张 32 位的 512*512 大小的高分辨率图标,用来在 Google Play 商店中显示。该图大小不能超过 1024KB。 proguard-project.txt Android IDE 和 ProGuard 使用的编译文件。可以通过编辑该文件来配置代码优化选项,以及发布版本的混淆设置。 project.properties Android IDE 中使用的编译文件。定义了应用程序的构建目标,以及其他编译系统选项。不要编辑这个文件 /src 必须的文件夹,包含所有的源代码 /gen 必须的文件夹,包含所有的自动生成的文件。 /gen/.../BuildConfig.java 调试应用程序时,该源文件自动生成。不要编辑 /gen/.../R.java 自动生成的资源管理的源文件。不要编辑 /assets 必须的文件夹。包含了项目中未编译的资源文件,一些你不想作为应用程序资源管理的应用程序数据(文件、目录) -
术语 描述 Context(上下文) 是 Android 应用的中央指挥中心。大部分应用特定的功能可以通过上下文访问或引用。Context 类是任何 Android 应用的基本构建模块,提供了访问应用程序范围的功能,譬如应用程序的私有文件、设备资源,以及整个系统的服务。应用程序的 Context 对象会被实例化为一个 Application 对象。 Activity(活动) 一个 Activity 类是 Context 类的子类,因此它也拥有 Context 类的所有功能。 Fragment(碎片) 一个活动有一个独特的任务或目的,但它可以进一步组件化,每一个组件被称为碎片。Fragment 类往往被用来组织活动的功能,从而允许在不同的屏幕大小、方向和纵横比上提供更灵活的用户体验。碎片常常用来在由多个 Activity 类组成的不同的屏幕上,使用相同的代码和屏幕逻辑放置相同的用户界面。 Intent(意图) Android 操作系统使用异步的消息传递机制,将任务匹配到合适的 Activity。每一个请求被打包成一个意图。使用 Intent 类是应用程序组件如活动和服务之间通信的主要方法。 Service(服务) 不需要用户交互的任务可以封装成一个服务。当需要处理耗时任务或需要定时处理时使用服务,用来处理后台操作。继承自 Context 类。 -
应用程序 Context
因为 Activity 类是由 Context 类派生的,故有时可以使用它而不是显示地获取应用程序 Context。但不要在任何情况下都使用 Activity Context,因为可能会导致内存泄漏。- 获取应用程序资源,如字符串、图形、xml 文件。
getResources()
- 访问应用程序首选项
getSharedPreferences()
- 管理私有的应用程序文件和目录
- 获取未编译的应用程序资产
getAssets()
- 访问系统服务
- 管理自由的应用程序数据库(SQLite)
- 以应用程序权限工作
- 获取应用程序资源,如字符串、图形、xml 文件。
-
Activity 生命周期
onCreate()
初始化静态 Activity 数据onResume()
初始化及取回 Activity 数据-
onPause()
停止、保存和释放 Activity 数据,保存重要数据到用久存储,使用 onSaveInstanceState() 保存一些可以从当前屏幕快速恢复的数据或某些不重要的信息(如 未提交的表单数据或任何其他减少用户麻烦的状态信息)。停止任何声音、视频、动画,也必须停用资源如数据库游标对象 或 其他 Activity 终止时应该清理的对象。onPause() 可能是 Activity 进入后台时最后用来清理或释放不需要的资源的机会,需要在这里保存任何未提交的数据。
调用 onPause() 后,系统保留杀死任何一个 Activity 而没有进一步通知的权利。
Activity 需要在 onPause() 方法中执行快速的代码,因为只有 onPause() 方法返回后,新的前台 Activity 才会启动。
一般来说,任何在 onResume() 方法中获取的资源和数据应该在 onPause() 方法中释放,否则当进程终止后,这些资源可能无法干净地释放。避免 Activity 被杀死
内存不足时 Android 操作系统可以杀死任何暂停、停止或销毁的 Activity。这基本意味着任何没有在前台的 Activity 都会面临被关闭的可能。
若 Activity 在 onPause() 后被杀掉,那么 onStop() 和 onDestroy() 方法将不会被调用。在 onPause() 方法内更多的释放 Activity 的资源,那么 Activity 就越不太可能在后台被直接杀掉(没有其他的状态切换方法被调用)。
杀死一个 Activity 并不会导致它从 Activity 堆栈中的移除。若 Activity 实现并使用了 onSaveInstanceState() 用于自定义数据,Activity 状态将被保存到 Bundle 对象中(虽然一些 View 数据将会被自动保存)。当用户返回到 Activity 后,onCreate() 方法会被再次调用,这次会有一个有效的 Bundle 对象作为方法参数。 -
onDestroy()
销毁静态 Activity 数据
当 Activity 通过正常的操作被销毁,onDestroy() 方法将会被调用。
onDestroy() 会在 2 种情况下被调用:Activity 自己完成了它的生命周期,或因为资源问题,Activity 被 Android 操作系统杀掉,但仍有足够的时间从容销毁 Activity(与不调用 onDestroy() 方法直接终止 Activity 不同)。若 Activity 是被 Android 操作系统杀掉的,isFinishing() 方法会返回 false。该方法在 onPause() 中十分有用,可以知道 Activity 是否能够恢复。
-
AndroidManifest.xml 清单文件
作用:- Android 操作系统使用清单文件来安装、更新和运行应用程序包
- 显示应用程序的详细信息,如名称、描述、图标
- 指定应用的系统需求,包括对 Android SDK 的支持、设备配置的需求(如方向键),以及应用依赖的平台功能(如多点触摸)
- 以市场过滤为目的,指定应用的哪些功能是必须的
- 注册应用的 Activity,并指定如何启动
- 管理应用程序的权限
- 配置其他高级的应用组件配置详细信息,包括定义 Service、Broadcast Receiver 及 Content Provider
- 为你的 Activity、Service 及 Broadcast Receiver 指定 Intent 过滤器
- 为应用测试开启应用设置,如调试和配置仪器
设置应用程序的系统需求:
-
<uses-feature>
标签用于指定应用需要哪些 Android 功能才能正常运行。这些设置只供参考不会强制使用。当使用<uses-feature>
标签时,可以指定android:required
的可选属性,并设置为 true 或 false,这个可以用于配置 Google Play 商店中的过滤。若该值为 true,Google Play 将只会在具有特定硬件或软件功能的设备上显示你的应用程序(如摄像头)。若应用程序需要多个功能,则必须为每个功能创建一个<uses-feature>
标签。若应用正常运行时并不需要一个特定的功能,与其在应用商店内过滤并限制特定的设备,还可以使用
getPackageManager().hasSystemFeature()
在运行时检查特定的设备功能,并在用户的设备上支持该功能时才允许特定的功能,这样可以最大化安装和使用你应用的人群。 <uses-sdk>
标签用于指定应用程序支持的 Android 平台版本。应用商店会根据应用的清单文件的<uses-sdk>
标签等的设置来为给定的用户过滤应用。忽略使用该标签将会在编译环境中产生一个警告信息。<uses-configuration>
标签用于指定应用程序支持的硬件或软件的输入方法。有 5 个方向的配置属性:硬件键盘和键盘类型;方向设备如方向键、轨迹球和滚轮;触摸屏的设置。若应用程序支持多种输入配置,则必须有多个<uses-configuration>
标签。<supports-screens>
标签用于指定应用支持的 Android 屏幕类型。- <application> 内的
<uses-library>
标签用于注册应用中链接到的外部库。 <supports-gl-texture>
用于指定应用支持的 GL 材质的压缩格式。使用图形库的应用使用该标签,并用于兼容可以支持指定压缩格式的设备。<application>
标签属性中设置应用程序范围的主题。<instrumentation>
设置单元测试功能。<activity-alias>
为 Activity 起别名。<receiver>
注册 Broadcast Receivers。<provider>
注册 Content Provider,使用<grant-uri-permission>
和<path-permission>
管理 Content Provider 的权限。<meta-data>
包含应用的 Activity、Service、Receiver 组件注册的其他数据。
-
管理资源
-
所有 Android 应用程序由 2 部分组成:
功能部分——代码指令(程序运行的任何算法)
数据部分——资源(文本字符串、样式主题、尺寸、图片图标、音视频文件等) -
默认 Android 资源目录,所有资源必须存放在项目的 /res 目录下的指定子目录,且目录名必须小写。
资源子目录 内容 /res/drawable-*/ 图形资源 /res/layout/ 用户界面资源 /res/menu/ 菜单资源,用于显示 Activity 中的选项或操作 /res/values/ 简单的数据,如字符串、样式主题、尺寸资源 /res/values-sw*/ 覆盖默认的尺寸资源 /res/values-v*/ 较新 API 自定义的样式和主题资源 -
常见的资源类型及存储结构
资源类型 所需目录 建议文件名 XML 标签 字符串 /values/ strings.xml <string>
字符串复数形式 /values/ strings.xml <plurals>, <item>
字符串数组 /values/ strings.xml 或 arrays.xml <string-array>, <item>
布尔类型 /values/ bools.xml <bool>
颜色 /values/ colors.xml <color>
颜色状态列表 /color/ 包括 buttonstates.xml, indicators.xml <selector>, <item>
尺寸 /values/ dimens.xml <dimen>
ID /values/ ids.xml <item>
整型 /values/ integers.xml <integer>
整型数组 /values/ integers.xml <integer-array>
混合类型数组 /values/ arrays.xml <array>, <item>
简单可绘制图形(可打印) /values/ drawables.xml <drawable>
XML 文件定义的图形(如形状) /drawable/ 包括 icon.png, logo.jpg 支持的图形文件或可绘制图形 补间动画 /anim/ 包括 fadesequence.xml, spinsequence.xml <set>, <alpha>, <scale>,<translate>, <rotate>
属性动画 /animator/ mypropanims.xml <set>, <objectAnimator>, <valueAnimator>
帧动画 /drawable/ 包括 sequence1.xml, sequence2.xml <animation-list>, <item>
菜单 /menu/ 包括 mainmenu.xml, helpmenu.xml <menu>
XML 文件 /xml/ 包括 data.xml, data2.xml 开发者定义 原始文件 /raw/ 包括 jingle.mp3, video.mp4, text.txt 开发者定义 布局 /layout/ 包括 main.xml, help.xml 多样,但必须是布局类型 样式和主题 /values/ styles.xml, themes.xml <style>
-
使用字符串资源
- 所有包含撇号及单引号的字符串需要被转义 "\" 或被双引号包裹
- 可使用 HTML 样式的属性(粗体<b>、斜体<i>、下划线<u>标签)
- 忽略格式:
String s = getResources().getString(R.string.hello);
- 保留字符串格式: ①
CharSequence cs = getResourece().getText(R.string.hello);
②使用 TextUtils 的 htmlEncode()
-
-
TexdView 文本中创建上下文链接:autoLink 属性
值 描述 note 禁用所有链接 web 允许 web 网页的 URL 链接 email 允许电子邮件地址链接,并在邮件客户端填写收件人 phone 允许电话号码链接,可以在拨号器应用中填写电话号码来拨打 map 允许街道地址的链接,可以在地图应用中显示位置 all 允许所有类型的链接 开启 autoLink 功能依赖于 Android SDK 中的各种类型的检测。有时候也可能链接不正确或产生误导。
-
EditText
-
输入过滤器限制输入
使用setFilters()
来设置 InputFilter 限制用户输入,InputFilter 接口包含一个filter()
方法。可以实现 InputFilter 接口来创建自定义的过滤器。setFilters()
的参数是 InputFilter 对象的数组,这对于组合多个过滤器是非常有用的。 -
自动完成
AutoCompleteTextView
:基于用户输入的内容来填写整个文本。- 允许用户输入字符串列表,每一个都具有自动完成功能。这些字符串都要以某种方式分隔,提供给
MultiAutoCompleteTextView
对象的Tokenizer
来处理。这对于指定通用标签等的列表有所帮助。也可以自己实现MultiAutoCompleteTextView.Tokenizer
接口,内置逗号分隔符CommaTokenizer()
。
-
<span style="font-size:14px;"><span style="font-size:14px;"><span style="font-size:14px;"> <span> </span> android:pathPattern="string"</span></span></span>