Android学习指南 — Android基础知识汇总,2024大厂Android社招面试题

| 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 | 服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。 |

流程

AIDL 通信


Android Interface Definition Language

使用示例:

  • 新建AIDL接口文件

// RemoteService.aidl

package com.example.mystudyapplication3;

interface IRemoteService {

int getUserId();

}

  • 创建远程服务

public class RemoteService extends Service {

private int mId = -1;

private Binder binder = new IRemoteService.Stub() {

@Override

public int getUserId() throws RemoteException {

return mId;

}

};

@Nullable

@Override

public IBinder onBind(Intent intent) {

mId = 1256;

return binder;

}

}

  • 声明远程服务

<service

android:name=“.RemoteService”

android:process=“:aidl” />

  • 绑定远程服务

public class MainActivity extends AppCompatActivity {

public static final String TAG = “wzq”;

IRemoteService iRemoteService;

private ServiceConnection mConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

iRemoteService = IRemoteService.Stub.asInterface(service);

try {

Log.d(TAG, String.valueOf(iRemoteService.getUserId()));

} catch (RemoteException e) {

e.printStackTrace();

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

iRemoteService = null;

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

bindService(new Intent(MainActivity.this, RemoteService.class), mConnection, Context.BIND_AUTO_CREATE);

}

}

Messenger


Messenger可以在不同进程中传递 Message 对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger 是一种轻量级的 IPC 方案,底层实现是 AIDL。

Window / WindowManager

======================

Window 概念与分类


Window 是一个抽象类,它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中所有的视图都是通过 Window 来呈现,因此 Window 实际是 View 的直接管理者。

| Window 类型 | 说明 | 层级 |

| — | — | — |

| Application Window | 对应着一个 Activity | 1~99 |

| Sub Window | 不能单独存在,只能附属在父 Window 中,如 Dialog 等 | 1000~1999 |

| System Window | 需要权限声明,如 Toast 和 系统状态栏等 | 2000~2999 |

Window 的内部机制


Window 是一个抽象的概念,每一个 Window 对应着一个 View 和一个 ViewRootImpl。Window 实际是不存在的,它是以 View 的形式存在。对 Window 的访问必须通过 WindowManager,WindowManager 的实现类是 WindowManagerImpl:

WindowManagerImpl.java

@Override

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

applyDefaultToken(params);

mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);

}

@Override

public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

applyDefaultToken(params);

mGlobal.updateViewLayout(view, params);

}

@Override

public void removeView(View view) {

mGlobal.removeView(view, false);

}

WindowManagerImpl 没有直接实现 Window 的三大操作,而是全部交给 WindowManagerGlobal 处理,WindowManagerGlobal 以工厂的形式向外提供自己的实例:

WindowManagerGlobal.java

// 添加

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow) {

···

// 子 Window 的话需要调整一些布局参数

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

if (parentWindow != null) {

parentWindow.adjustLayoutParamsForSubWindow(wparams);

} else {

···

}

ViewRootImpl root;

View panelParentView = null;

synchronized (mLock) {

// 新建一个 ViewRootImpl,并通过其 setView 来更新界面完成 Window 的添加过程

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

img
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
img

最后

代码真的是重质不重量,质量高的代码,是当前代码界提倡的,当然写出高质量的代码肯定需要一个相当高的专业素养,这需要在日常的代码书写中逐渐去吸收掌握,谁不是每天都在学习呀,目的还不是为了一个,为实现某个功能写出高质量的代码。

所以,长征路还长,大家还是好好地做个务实的程序员吧。

最后,小编这里有一系列Android提升学习资料,有兴趣的小伙伴们可以来看下哦~

params);

mGlobal.updateViewLayout(view, params);

}

@Override

public void removeView(View view) {

mGlobal.removeView(view, false);

}

WindowManagerImpl 没有直接实现 Window 的三大操作,而是全部交给 WindowManagerGlobal 处理,WindowManagerGlobal 以工厂的形式向外提供自己的实例:

WindowManagerGlobal.java

// 添加

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow) {

···

// 子 Window 的话需要调整一些布局参数

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

if (parentWindow != null) {

parentWindow.adjustLayoutParamsForSubWindow(wparams);

} else {

···

}

ViewRootImpl root;

View panelParentView = null;

synchronized (mLock) {

// 新建一个 ViewRootImpl,并通过其 setView 来更新界面完成 Window 的添加过程

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-NUQ6ft1X-1711549169844)]
[外链图片转存中…(img-bUrlPNbg-1711549169845)]
[外链图片转存中…(img-jfbgh6Tn-1711549169845)]
img
[外链图片转存中…(img-eorP82ML-1711549169846)]
[外链图片转存中…(img-KdBjVRqy-1711549169846)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

[外链图片转存中…(img-kdd07VMS-1711549169846)]
[外链图片转存中…(img-OdLYItQo-1711549169847)]

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
[外链图片转存中…(img-iOnTNUfd-1711549169847)]

最后

代码真的是重质不重量,质量高的代码,是当前代码界提倡的,当然写出高质量的代码肯定需要一个相当高的专业素养,这需要在日常的代码书写中逐渐去吸收掌握,谁不是每天都在学习呀,目的还不是为了一个,为实现某个功能写出高质量的代码。

所以,长征路还长,大家还是好好地做个务实的程序员吧。

最后,小编这里有一系列Android提升学习资料,有兴趣的小伙伴们可以来看下哦~

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值