mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
…
r.paused = true;
mActivities.put(r.token, r);
}
return activity;
}
Fragment
========
特点
–
-
Fragment 解决 Activity 间的切换不流畅,轻量切换
-
可以从 startActivityForResult 中接收到返回结果,但是View不能
-
只能在 Activity 保存其状态(用户离开 Activity)之前使用 commit() 提交事务。如果您试图在该时间点后提交,则会引发异常。 这是因为如需恢复 Activity,则提交后的状态可能会丢失。 对于丢失提交无关紧要的情况,请使用 commitAllowingStateLoss()。
生命周期
与Activity通信
执行此操作的一个好方法是,在片段内定义一个回调接口,并要求宿主 Activity 实现它。
public static class FragmentA extends ListFragment {
…
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
…
}
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
…
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString());
}
}
…
}
Service
=======
Service 分为两种工作状态,一种是启动状态,主要用于执行后台计算;另一种是绑定状态,主要用于其他组件和 Service 的交互。
启动过程
ActivityThread.java
@UnsupportedAppUsage
private void handleCreateService(CreateServiceData data) {
···
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
}
···
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
···
}
绑定过程
ActivityThread.java
private void handleBindService(BindServiceData data) {
Service s = mServices.get(data.token);
···
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
data.intent.prepareToEnterProcess();
try {
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
ActivityManager.getService().publishService(
data.token, data.intent, binder);
} else {
s.onRebind(data.intent);
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
···
}
}
生命周期
| 值 | 说明 |
| — | — |
| START_NOT_STICKY | 如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务 |
| START_STICKY | 如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务 |
| START_REDELIVER_INTENT | 如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务 |
启用前台服务
Notification notification = new Notification(icon, text, System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, title, mmessage, pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
BroadcastReceiver
=================
target 26 之后,无法在 AndroidManifest 显示声明大部分广播,除了一部分必要的广播,如:
-
ACTION_BOOT_COMPLETED
-
ACTION_TIME_SET
-
ACTION_LOCALE_CHANGED
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(receiver, filter);
注册过程
ContentProvider
===============
ContentProvider 管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。 内容提供程序是连接一个进程中的数据与另一个进程中运行的代码的标准界面。
ContentProvider 无法被用户感知,对于一个 ContentProvider 组件来说,它的内部需要实现增删该查这四种操作,它的内部维持着一份数据集合,这个数据集合既可以是数据库实现,也可以是其他任何类型,如 List 和 Map,内部的 insert、delete、update、query 方法需要处理好线程同步,因为这几个方法是在 Binder 线程池中被调用的。
ContentProvider 通过 Binder 向其他组件乃至其他应用提供数据。当 ContentProvider 所在的进程启动时,ContentProvider 会同时启动并发布到 AMS 中,需要注意的是,这个时候 ContentProvider 的 onCreate 要先于 Application 的 onCreate 而执行。
基本使用
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table
mProjection, // The columns to return for each row
mSelectionClause // Selection criteria
mSelectionArgs, // Selection criteria
mSortOrder); // The sort order for the returned rows
public class Installer extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
ContentProvider 和 sql 在实现上有什么区别?
- ContentProvider 屏蔽了数据存储的细节,内部实现透明化,用户只需关心 uri 即可(是否匹配)
- ContentProvider 能实现不同 app 的数据共享,sql 只能是自己程序才能访问
- Contentprovider 还能增删本地的文件,xml等信息
数据存储
====
| 存储方式 | 说明 |
| — | — |
| SharedPreferences | 在键值对中存储私有原始数据 |
| 内部存储 | 在设备内存中存储私有数据 |
| 外部存储 | 在共享的外部存储中存储公共数据 |
| SQLite 数据库 | 在私有数据库中存储结构化数据 |
View
====
ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联
View 的整个绘制流程可以分为以下三个阶段:
-
measure: 判断是否需要重新计算 View 的大小,需要的话则计算
-
layout: 判断是否需要重新计算 View 的位置,需要的话则计算
-
draw: 判断是否需要重新绘制 View,需要的话则重绘制
MeasureSpec
MeasureSpec表示的是一个32位的整形值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize。MeasureSpec 是 View 类的一个静态内部类,用来说明应该如何测量这个 View
| Mode | 说明 |
| — | — |
| UNSPECIFIED | 不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。 |
| EXACTLY | 精确测量模式,视图宽高指定为 match_parent 或具体数值时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值 |
| AT_MOST | 最大值测量模式,当视图的宽高指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸 |
对于 DecorView 而言,它的MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定;对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和其自身的 LayoutParams 共同决定
| childLayoutParams/parentSpecMode | EXACTLY | AT_MOST |
| — | — | — |
| dp/px | EXACTLY(childSize) | EXACTLY(childSize) |
| match_parent | EXACTLY(childSize) | AT_MOST(parentSize) |
| wrap_content | AT_MOST(parentSize) | AT_MOST(parentSize) |
直接继承 View 的控件需要重写 onMeasure 方法并设置 wrap_content 时的自身大小,因为 View 在布局中使用 wrap_content,那么它的 specMode 是 AT_MOST 模式,在这种模式下,它的宽/高等于父容器当前剩余的空间大小,就相当于使用 match_parent。这解决方式如下:
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);
// 在 wrap_content 的情况下指定内部宽/高(mWidth 和 mHeight`)
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(widthSpecSize, mHeight);
}
}
MotionEvent
| 事件 | 说明 |
| — | — |
| ACTION_DOWN | 手指刚接触到屏幕 |
| ACTION_MOVE | 手指在屏幕上移动 |
| ACTION_UP | 手机从屏幕上松开的一瞬间 |
| ACTION_CANCEL | 触摸事件取消 |
点击屏幕后松开,事件序列为 DOWN -> UP,点击屏幕滑动松开,事件序列为 DOWN -> MOVE -> …> MOVE -> UP。
getX/getY
返回相对于当前View左上角的坐标,getRawX/getRawY
返回相对于屏幕左上角的坐标
TouchSlop是系统所能识别出的被认为滑动的最小距离,不同设备值可能不相同,可通过 ViewConfiguration.get(getContext()).getScaledTouchSlop()
获取。
VelocityTracker
VelocityTracker 可用于追踪手指在滑动中的速度:
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
velocityTracker.clear();
velocityTracker.recycle();
return false;
}
});
GestureDetector
GestureDetector 辅助检测用户的单击、滑动、长按、双击等行为:
final GestureDetector mGestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) { return false; }
@Override
public void onShowPress(MotionEvent e) { }
@Override
public boolean onSingleTapUp(MotionEvent e) { return false; }
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; }
@Override
public void onLongPress(MotionEvent e) { }
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }
});
mGestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) { return false; }
@Override
public boolean onDoubleTap(MotionEvent e) { return false; }
@Override
public boolean onDoubleTapEvent(MotionEvent e) { return false; }
});
// 解决长按屏幕后无法拖动的问题
mGestureDetector.setIsLongpressEnabled(false);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});
如果是监听滑动相关,建议在 onTouchEvent
中实现,如果要监听双击,那么就使用 GestureDectector
。
Scroller
弹性滑动对象,用于实现 View 的弹性滑动,Scroller 本身无法让 View 弹性滑动,需要和 View 的 computeScroll
方法配合使用。startScroll
方法是无法让 View 滑动的,invalidate
会导致 View 重绘,重回后会在 draw
方法中又会去调用 computeScroll
方法,computeScroll
方法又会去向 Scroller 获取当前的 scrollX 和 scrollY,然后通过 scrollTo
方法实现滑动,接着又调用 postInvalidate
方法如此反复。
Scroller mScroller = new Scroller(mContext);
private void smoothScrollTo(int destX) {
int scrollX = getScrollX();
int delta = destX - scrollX;
// 1000ms 内滑向 destX,效果就是慢慢滑动
mScroller.startScroll(scrollX, 0 , delta, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
View 的滑动
scrollTo/scrollBy
适合对 View 内容的滑动。scrollBy
实际上也是调用了 scrollTo
方法:
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
mScrollX的值等于 View 的左边缘和 View 内容左边缘在水平方向的距离,mScrollY的值等于 View 上边缘和 View 内容上边缘在竖直方向的距离。scrollTo
和 scrollBy
只能改变 View 内容的位置而不能改变 View 在布局中的位置。
- 使用动画
操作简单,主要适用于没有交互的 View 和实现复杂的动画效果。
- 改变布局参数 操作稍微复杂,适用于有交互的 View.
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
view.requestLayout();
//或者 view.setLayoutParams(params);
View 的事件分发
点击事件达到顶级 View(一般是一个 ViewGroup),会调用 ViewGroup 的 dispatchTouchEvent 方法,如果顶级 ViewGroup 拦截事件即 onInterceptTouchEvent 返回 true,则事件由 ViewGroup 处理,这时如果 ViewGroup 的 mOnTouchListener 被设置,则 onTouch 会被调用,否则 onTouchEvent 会被调用。也就是说如果都提供的话,onTouch 会屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果设置了 mOnClickListenser,则 onClick 会被调用。如果顶级 ViewGroup 不拦截事件,则事件会传递给它所在的点击事件链上的子 View,这时子 View 的 dispatchTouchEvent 会被调用。如此循环。
-
ViewGroup 默认不拦截任何事件。ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。
-
View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,onTouchEvent 方法就会被调用。
-
View 在可点击状态下,onTouchEvent 默认会消耗事件。
-
ACTION_DOWN 被拦截了,onInterceptTouchEvent 方法执行一次后,就会留下记号(mFirstTouchTarget == null)那么往后的 ACTION_MOVE 和 ACTION_UP 都会拦截。`
在 Activity 中获取某个 View 的宽高
- Activity/View#onWindowFocusChanged
// 此时View已经初始化完毕
// 当Activity的窗口得到焦点和失去焦点时均会被调用一次
// 如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasureWidth();
int height = view.getMeasuredHeight();
}
}
- view.post(runnable)
// 通过post可以将一个runnable投递到消息队列的尾部,// 然后等待Looper调用次runnable的时候,View也已经初
// 始化好了
protected void onStart() {
super.onStart();
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
- ViewTreeObserver
// 当View树的状态发生改变或者View树内部的View的可见// 性发生改变时,onGlobalLayout方法将被回调
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings(“deprecation”)
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
Draw 的基本流程
// 绘制基本上可以分为六个步骤
public void draw(Canvas canvas) {
…
// 步骤一:绘制View的背景
drawBackground(canvas);
…
// 步骤二:如果需要的话,保持canvas的图层,为fading做准备
saveCount = canvas.getSaveCount();
…
canvas.saveLayer(left, top, right, top + length, null, flags);
…
// 步骤三:绘制View的内容
onDraw(canvas);
…
// 步骤四:绘制View的子View
dispatchDraw(canvas);
…
// 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
…
canvas.restoreToCount(saveCount);
…
// 步骤六:绘制View的装饰(例如滚动条等等)
onDrawForeground(canvas)
}
自定义 View
- 继承 View 重写
onDraw
方法
主要用于实现一些不规则的效果,静态或者动态地显示一些不规则的图形,即重写 onDraw
方法。采用这种方式需要自己支持 wrap_content,并且 padding 也需要自己处理。
- 继承 ViewGroup 派生特殊的 Layout
主要用于实现自定义布局,采用这种方式需要合适地处理 ViewGroup 的测量、布局两个过程,并同时处理子元素的测量和布局过程。
- 继承特定的 View
用于扩张某种已有的View的功能
- 继承特定的 ViewGroup
用于扩张某种已有的ViewGroup的功能
进程
==
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。
各类组件元素的清单文件条目<activity>
、<service>
、<receiver>
和 <provider>
—均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。
进程生命周期
1、前台进程
-
托管用户正在交互的 Activity(已调用 Activity 的
onResume()
方法) -
托管某个 Service,后者绑定到用户正在交互的 Activity
-
托管正在“前台”运行的 Service(服务已调用
startForeground()
) -
托管正执行一个生命周期回调的 Service(
onCreate()
、onStart()
或onDestroy()
) -
托管正执行其
onReceive()
方法的 BroadcastReceiver
2、可见进程
-
托管不在前台、但仍对用户可见的 Activity(已调用其
onPause()
方法)。例如,如果 re前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。 -
托管绑定到可见(或前台)Activity 的 Service
3、服务进程
- 正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。
4、后台进程
- 包含目前对用户不可见的 Activity 的进程(已调用 Activity 的
onStop()
方法)。通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。
5、空进程
- 不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。\
多进程
如果注册的四大组件中的任意一个组件时用到了多进程,运行该组件时,都会创建一个新的 Application 对象。对于多进程重复创建 Application 这种情况,只需要在该类中对当前进程加以判断即可。
public class MyApplication extends Application {
@Override
public void onCreate() {
Log.d(“MyApplication”, getProcessName(android.os.Process.myPid()));
super.onCreate();
}
/**
-
根据进程 ID 获取进程名
-
@param pid 进程id
-
@return 进程名
*/
public String getProcessName(int pid){
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processInfoList = am.getRunningAppProcesses();
if (processInfoList == null) {
return null;
}
for (ActivityManager.RunningAppProcessInfo processInfo : processInfoList) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
return null;
}
}
一般来说,使用多进程会造成以下几个方面的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences 的可靠性下降
- Application 会多次创建
进程存活
OOM_ADJ
| ADJ级别 | 取值 | 解释 |
| — | — | — |
| UNKNOWN_ADJ | 16 | 一般指将要会缓存进程,无法获取确定值 |
| CACHED_APP_MAX_ADJ | 15 | 不可见进程的adj最大值 |
| CACHED_APP_MIN_ADJ | 9 | 不可见进程的adj最小值 |
| SERVICE_B_AD | 8 | B List 中的 Service(较老的、使用可能性更小) |
| PREVIOUS_APP_ADJ | 7 | 上一个App的进程(往往通过按返回键) |
| HOME_APP_ADJ | 6 | Home进程 |
| SERVICE_ADJ | 5 | 服务进程(Service process) |
| HEAVY_WEIGHT_APP_ADJ | 4 | 后台的重量级进程,system/rootdir/init.rc 文件中设置 |
| BACKUP_APP_ADJ | 3 | 备份进程 |
| PERCEPTIBLE_APP_ADJ | 2 | 可感知进程,比如后台音乐播放 |
| VISIBLE_APP_ADJ | 1 | 可见进程(Visible process) |
| FOREGROUND_APP_ADJ | 0 | 前台进程(Foreground process) |
| PERSISTENT_SERVICE_ADJ | -11 | 关联着系统或persistent进程 |
| PERSISTENT_PROC_ADJ | -12 | 系统 persistent 进程,比如telephony |
| SYSTEM_ADJ | -16 | 系统进程 |
| NATIVE_ADJ | -17 | native进程(不被系统管理) |
进程被杀情况
进程保活方案
-
开启一个像素的 Activity
-
使用前台服务
-
多进程相互唤醒
-
JobSheduler 唤醒
-
粘性服务 & 与系统服务捆绑
Parcelable 接口
=============
只要实现了 Parcelable 接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。
使用示例
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
private int userId;
protected User(Parcel in) {
userId = in.readInt();
}
public static final Creator CREATOR = new Creator() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
}
public int getUserId() {
return userId;
}
}
方法说明
Parcel 内部包装了可序列化的数据,可以在 Binder 中自由传输。序列化功能由 writeToParcel
方法完成,最终是通过 Parcel 中的一系列 write 方法完成。反序列化功能由 CREATOR 来完成,通过 Parcel 的一系列 read 方法来完成反序列化过程。
| 方法 | 功能 |
| — | — |
| createFromParcel(Parcel in) | 从序列化后的对象中创建原始对象 |
| newArray(int size) | 创建指定长度的原始对象数组 |
| User(Parcel in) | 从序列化后的对象中创建原始对象 |
| writeToParcel(Parcel dest, int flags) | 将当前对象写入序列化结构中,其中 flags 标识有两种值:0 或者 1。为 1 时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0 |
| describeContents | 返回当前对象的内容描述。如果含有文件描述符,返回 1,否则返回 0,几乎所有情况都返回 0 |
Parcelable 与 Serializable 对比
-
Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接在内存中读写
-
Serializable 会使用反射,序列化和反序列化过程需要大量 I/O 操作, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多
IPC
===
IPC 即 Inter-Process Communication (进程间通信)。Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。
在 Linux 系统中,虚拟内存机制为每个进程分配了线性连续的内存空间,操作系统将这种虚拟内存空间映射到物理内存空间,每个进程有自己的虚拟内存空间,进而不能操作其他进程的内存空间,只有操作系统才有权限操作物理内存空间。 进程隔离保证了每个进程的内存安全。
IPC方式
| 名称 | 优点 | 缺点 | 适用场景 |
| — | — | — | — |
| Bundle | 简单易用 | 只能传输 Bundle 支持的数据类型 | 四大组件间的进程间通信 |
| 文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
| AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有 RPC 需求 |
| Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很处理高并发清醒,不支持 RPC,数据通过 Message 进行传输,因此只能传输 Bundle 支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求 |
| ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作 | 可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作 | 一对多的进程间数据共享 |
| Socket | 可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点烦琐,不支持直接的RPC | 网络数据交换 |
Binder
Binder 是 Android 中的一个类,实现了 IBinder 接口。从 IPC 角度来说,Binder 是 Android 中的一种扩进程通信方方式。从 Android 应用层来说,Binder 是客户端和服务器端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象。
Binder 相较于传统 IPC 来说更适合于Android系统,具体原因的包括如下三点:
-
Binder 本身是 C/S 架构的,这一点更符合 Android 系统的架构
-
性能上更有优势:管道,消息队列,Socket 的通讯都需要两次数据拷贝,而 Binder 只需要一次。要知道,对于系统底层的 IPC 形式,少一次数据拷贝,对整体性能的影响是非常之大的
-
安全性更好:传统 IPC 形式,无法得到对方的身份标识(UID/GID),而在使用 Binder IPC 时,这些身份标示是跟随调用过程而自动传递的。Server 端很容易就可以知道 Client 端的身份,非常便于做安全检查
示例:
- 新建AIDL接口文件
RemoteService.aidl
package com.example.mystudyapplication3;
interface IRemoteService {
int getUserId();
}
系统会自动生成 IRemoteService.java
:
/*
- This file is auto-generated. DO NOT MODIFY.
*/
package com.example.mystudyapplication3;
// Declare any non-default types here with import statements
//import com.example.mystudyapplication3.IUserBean;
public interface IRemoteService extends android.os.IInterface {
/**
- Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.mystudyapplication3.IRemoteService {
private static final java.lang.String DESCRIPTOR = “com.example.mystudyapplication3.IRemoteService”;
/**
- Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
-
Cast an IBinder object into an com.example.mystudyapplication3.IRemoteService interface,
-
generating a proxy if needed.
*/
public static com.example.mystudyapplication3.IRemoteService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.mystudyapplication3.IRemoteService))) {
return ((com.example.mystudyapplication3.IRemoteService) iin);
}
return new com.example.mystudyapplication3.IRemoteService.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getUserId: {
data.enforceInterface(descriptor);
int _result = this.getUserId();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.mystudyapplication3.IRemoteService {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int getUserId() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getUserId, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getUserId = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int getUserId() throws android.os.RemoteException;
}
| 方法 | 含义 |
| — | — |
| DESCRIPTOR | Binder 的唯一标识,一般用当前的 Binder 的类名表示 |
| asInterface(IBinder obj) | 将服务端的 Binder 对象成客户端所需的 AIDL 接口类型对象,这种转换过程是区分进程的,如果位于同一进程,返回的就是 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。 |
| asBinder | 用于返回当前 Binder 对象 |
| onTransact | 运行在服务端中的 Binder 线程池中,远程请求会通过系统底层封装后交由此方法来处理 |
| 定向 tag | 含义 |
| — | — |
| in | 数据只能由客户端流向服务端,服务端将会收到客户端对象的完整数据,客户端对象不会因为服务端对传参的修改而发生变动。 |
| out | 数据只能由服务端流向客户端,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。 |
| inout | 服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。 |
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
如果你进阶的路上缺乏方向,可以加入我们的圈子和安卓开发者们一起学习交流!
-
Android进阶学习全套手册
-
Android对标阿里P7学习视频
-
BATJ大厂Android高频面试题
最后,借用我最喜欢的乔布斯语录,作为本文的结尾:
人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。
你的时间有限,所以不要为别人而活。不要被教条所限,不要活在别人的观念里。不要让别人的意见左右自己内心的声音。
最重要的是,勇敢的去追随自己的心灵和直觉,只有自己的心灵和直觉才知道你自己的真实想法,其他一切都是次要。
s android.os.RemoteException;
}
| 方法 | 含义 |
| — | — |
| DESCRIPTOR | Binder 的唯一标识,一般用当前的 Binder 的类名表示 |
| asInterface(IBinder obj) | 将服务端的 Binder 对象成客户端所需的 AIDL 接口类型对象,这种转换过程是区分进程的,如果位于同一进程,返回的就是 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。 |
| asBinder | 用于返回当前 Binder 对象 |
| onTransact | 运行在服务端中的 Binder 线程池中,远程请求会通过系统底层封装后交由此方法来处理 |
| 定向 tag | 含义 |
| — | — |
| in | 数据只能由客户端流向服务端,服务端将会收到客户端对象的完整数据,客户端对象不会因为服务端对传参的修改而发生变动。 |
| out | 数据只能由服务端流向客户端,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。 |
| inout | 服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。 |
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-VWTUBkcF-1711736039388)]
[外链图片转存中…(img-GdvDXBqr-1711736039388)]
[外链图片转存中…(img-eXGGoGN7-1711736039389)]
[外链图片转存中…(img-dtvEesjp-1711736039389)]
[外链图片转存中…(img-jgthCFXd-1711736039389)]
[外链图片转存中…(img-0Gs1v9EJ-1711736039390)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-ltKfHywv-1711736039390)]
如果你进阶的路上缺乏方向,可以加入我们的圈子和安卓开发者们一起学习交流!
-
Android进阶学习全套手册
[外链图片转存中…(img-bSbLVXcj-1711736039390)]
-
Android对标阿里P7学习视频
[外链图片转存中…(img-DWthzvB9-1711736039390)]
-
BATJ大厂Android高频面试题
[外链图片转存中…(img-SJ0LfeaE-1711736039391)]
最后,借用我最喜欢的乔布斯语录,作为本文的结尾:
人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。
你的时间有限,所以不要为别人而活。不要被教条所限,不要活在别人的观念里。不要让别人的意见左右自己内心的声音。
最重要的是,勇敢的去追随自己的心灵和直觉,只有自己的心灵和直觉才知道你自己的真实想法,其他一切都是次要。