Android学习指南 — Android基础知识汇总

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 的添加过程

···

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

// do this last because it fires off messages to start doing things

try {

root.setView(view, wparams, panelParentView);

} catch (RuntimeException e) {

// BadTokenException or InvalidDisplayException, clean up.

if (index >= 0) {

removeViewLocked(index, true);

}

throw e;

}

}

}

// 删除

@UnsupportedAppUsage

public void removeView(View view, boolean immediate) {

···

synchronized (mLock) {

int index = findViewLocked(view, true);

View curView = mRoots.get(index).getView();

removeViewLocked(index, immediate);

···

}

}

private void removeViewLocked(int index, boolean immediate) {

ViewRootImpl root = mRoots.get(index);

View view = root.getView();

if (view != null) {

InputMethodManager imm = InputMethodManager.getInstance();

if (imm != null) {

imm.windowDismissed(mViews.get(index).getWindowToken());

}

}

boolean deferred = root.die(immediate);

if (view != null) {

view.assignParent(null);

if (deferred) {

mDyingViews.add(view);

}

}

}

// 更新

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

···

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

view.setLayoutParams(wparams);

synchronized (mLock) {

int index = findViewLocked(view, true);

ViewRootImpl root = mRoots.get(index);

mParams.remove(index);

mParams.add(index, wparams);

root.setLayoutParams(wparams, false);

}

}

在 ViewRootImpl 中最终会通过 WindowSession 来完成 Window 的添加、更新、删除工作,mWindowSession 的类型是 IWindowSession,是一个 Binder 对象,真正地实现类是 Session,是一个 IPC 过程。

Window 的创建过程


Activity 的 Window 创建过程

在 Activity 的创建过程中,最终会由 ActivityThread 的 performLaunchActivity() 来完成整个启动过程,该方法内部会通过类加载器创建 Activity 的实例对象,并调用 attach 方法关联一系列上下文环境变量。在 Activity 的 attach 方法里,系统会创建所属的 Window 对象并设置回调接口,然后在 Activity 的 setContentView 方法中将视图附属在 Window 上:

Activity.java

final void attach(Context context, ActivityThread aThread,

Instrumentation instr, IBinder token, int ident,

Application application, Intent intent, ActivityInfo info,

CharSequence title, Activity parent, String id,

NonConfigurationInstances lastNonConfigurationInstances,

Configuration config, String referrer, IVoiceInteractor voiceInteractor,

Window window, ActivityConfigCallback activityConfigCallback) {

attachBaseContext(context);

mFragments.attachHost(null /parent/);

mWindow = new PhoneWindow(this, window, activityConfigCallback);

mWindow.setWindowControllerCallback(this);

mWindow.setCallback(this);

mWindow.setOnWindowDismissedCallback(this);

mWindow.getLayoutInflater().setPrivateFactory(this);

if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {

mWindow.setSoftInputMode(info.softInputMode);

}

if (info.uiOptions != 0) {

mWindow.setUiOptions(info.uiOptions);

}

···

}

···

public void setContentView(@LayoutRes int layoutResID) {

getWindow().setContentView(layoutResID);

initWindowDecorActionBar();

}

PhoneWindow.java

@Override

public void setContentView(int layoutResID) {

if (mContentParent == null) { // 如果没有 DecorView,就创建

installDecor();

} else {

mContentParent.removeAllViews();

}

mLayoutInflater.inflate(layoutResID, mContentParent);

final Callback cb = getCallback();

if (cb != null && !isDestroyed()) {

// 回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生改变

cb.onContentChanged();

}

}

这个时候 DecorView 还没有被 WindowManager 正式添加。在 ActivityThread 的 handleResumeActivity 方法中,首先会调用 Activity 的 onResume 方法,接着调用 Activity 的 makeVisible(),完成 DecorView 的添加和显示过程:

Activity.java

void makeVisible() {

if (!mWindowAdded) {

ViewManager wm = getWindowManager();

wm.addView(mDecor, getWindow().getAttributes());

mWindowAdded = true;

}

mDecor.setVisibility(View.VISIBLE);

}

Dialog 的 Window 创建过程

Dialog 的 Window 的创建过程和 Activity 类似,创建同样是通过 PolicyManager 的 makeNewWindow 方法完成的,创建后的对象实际就是 PhoneWindow。当 Dialog 被关闭时,会通过 WindowManager 来移除 DecorView:mWindowManager.removeViewImmediate(mDecor)。

Dialog.java

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {

···

mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

final Window w = new PhoneWindow(mContext);

mWindow = w;

w.setCallback(this);

w.setOnWindowDismissedCallback(this);

w.setOnWindowSwipeDismissedCallback(() -> {

if (mCancelable) {

cancel();

}

});

w.setWindowManager(mWindowManager, null, null);

w.setGravity(Gravity.CENTER);

mListenersHandler = new ListenersHandler(this);

}

普通 Dialog 必须采用 Activity 的 Context,采用 Application 的 Context 就会报错,是因为应用 token 所导致,应用 token 一般只有 Activity 拥有。系统 Window 比较特殊,不需要 token。

Toast 的 Window 创建过程

Toast 属于系统 Window ,由于其具有定时取消功能,所以系统采用了 Handler。Toast 的内部有两类 IPC 过程,第一类是 Toast 访问 NotificationManagerService,第二类是 NotificationManagerService 回调 Toast 里的 TN 接口。

Toast 内部的视图由两种方式,一种是系统默认的样式,另一种是 setView 指定一个自定义 View,它们都对应 Toast 的一个内部成员 mNextView。

Toast.java

public void show() {

if (mNextView == null) {

throw new RuntimeException(“setView must have been called”);

}

INotificationManager service = getService();

String pkg = mContext.getOpPackageName();

TN tn = mTN;

tn.mNextView = mNextView;

try {

service.enqueueToast(pkg, tn, mDuration);

} catch (RemoteException e) {

// Empty

}

}

···

public void cancel() {

mTN.cancel();

}

NotificationManagerService.java

private void showNextToastLocked() {

ToastRecord record = mToastQueue.get(0);

while (record != null) {

if (DBG) Slog.d(TAG, “Show pkg=” + record.pkg + " callback=" + record.callback);

try {

record.callback.show();

scheduleTimeoutLocked(record, false);

return;

} catch (RemoteException e) {

Slog.w(TAG, "Object died trying to show notification " + record.callback

  • " in package " + record.pkg);

// remove it from the list and let the process die

int index = mToastQueue.indexOf(record);

if (index >= 0) {

mToastQueue.remove(index);

}

keepProcessAliveLocked(record.pid);

if (mToastQueue.size() > 0) {

record = mToastQueue.get(0);

} else {

record = null;

}

}

}

}

···

private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)

{

Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);

long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);

mHandler.removeCallbacksAndMessages®;

mHandler.sendMessageDelayed(m, delay);

}

Bitmap

======

配置信息与压缩方式


Bitmap 中有两个内部枚举类:

  • Config 是用来设置颜色配置信息

  • CompressFormat 是用来设置压缩方式

| Config | 单位像素所占字节数 | 解析 |

| — | — | — |

| Bitmap.Config.ALPHA_8 | 1 | 颜色信息只由透明度组成,占8位 |

| Bitmap.Config.ARGB_4444 | 2 | 颜色信息由rgba四部分组成,每个部分都占4位,总共占16位 |

| Bitmap.Config.ARGB_8888 | 4 | 颜色信息由rgba四部分组成,每个部分都占8位,总共占32位。是Bitmap默认的颜色配置信息,也是最占空间的一种配置 |

| Bitmap.Config.RGB_565 | 2 | 颜色信息由rgb三部分组成,R占5位,G占6位,B占5位,总共占16位 |

| RGBA_F16 | 8 | Android 8.0 新增(更丰富的色彩表现HDR) |

| HARDWARE | Special | Android 8.0 新增 (Bitmap直接存储在graphic memory) |

通常我们优化 Bitmap 时,当需要做性能优化或者防止 OOM,我们通常会使用 Bitmap.Config.RGB_565 这个配置,因为 Bitmap.Config.ALPHA_8 只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444 显示图片不清楚, Bitmap.Config.ARGB_8888 占用内存最多。

| CompressFormat | 解析 |

| — | — |

| Bitmap.CompressFormat.JPEG | 表示以 JPEG 压缩算法进行图像压缩,压缩后的格式可以是 .jpg 或者 .jpeg,是一种有损压缩 |

| Bitmap.CompressFormat.PNG | 颜色信息由 rgba 四部分组成,每个部分都占 4 位,总共占 16 位 |

| Bitmap.Config.ARGB_8888 | 颜色信息由 rgba 四部分组成,每个部分都占 8 位,总共占 32 位。是 Bitmap 默认的颜色配置信息,也是最占空间的一种配置 |

| Bitmap.Config.RGB_565 | 颜色信息由 rgb 三部分组成,R 占 5 位,G 占 6 位,B 占 5 位,总共占 16 位 |

常用操作


裁剪、缩放、旋转、移动

Matrix matrix = new Matrix();

// 缩放

matrix.postScale(0.8f, 0.9f);

// 左旋,参数为正则向右旋

matrix.postRotate(-45);

// 平移, 在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作

matrix.postTranslate(100, 80);

// 裁剪并执行以上操作

Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);

虽然Matrix还可以调用postSkew方法进行倾斜操作,但是却不可以在此时创建Bitmap时使用。

Bitmap与Drawable转换

// Drawable -> Bitmap

public static Bitmap drawableToBitmap(Drawable drawable) {

Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);

Canvas canvas = new Canvas(bitmap);

drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight();

drawable.draw(canvas);

return bitmap;

}

// Bitmap -> Drawable

public static Drawable bitmapToDrawable(Resources resources, Bitmap bm) {

Drawable drawable = new BitmapDrawable(resources, bm);

return drawable;

}

保存与释放

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);

File file = new File(getFilesDir(),“test.jpg”);

if(file.exists()){

file.delete();

}

try {

FileOutputStream outputStream=new FileOutputStream(file);

bitmap.compress(Bitmap.CompressFormat.JPEG,90,outputStream);

outputStream.flush();

outputStream.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

//释放bitmap的资源,这是一个不可逆转的操作

bitmap.recycle();

图片压缩

public static Bitmap compressImage(Bitmap image) {

if (image == null) {

return null;

}

ByteArrayOutputStream baos = null;

try {

baos = new ByteArrayOutputStream();

image.compress(Bitmap.CompressFormat.JPEG, 100, baos);

byte[] bytes = baos.toByteArray();

ByteArrayInputStream isBm = new ByteArrayInputStream(bytes);

Bitmap bitmap = BitmapFactory.decodeStream(isBm);

return bitmap;

} catch (OutOfMemoryError e) {

e.printStackTrace();

} finally {

try {

if (baos != null) {

baos.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

return null;

}

BitmapFactory


Bitmap创建流程

Option类

| 常用方法 | 说明 |

| — | — |

| boolean inJustDecodeBounds | 如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息 |

| int inSampleSize | 图片缩放的倍数 |

| int outWidth | 获取图片的宽度值 |

| int outHeight | 获取图片的高度值 |

| int inDensity | 用于位图的像素压缩比 |

| int inTargetDensity | 用于目标位图的像素压缩比(要生成的位图) |

| byte[] inTempStorage | 创建临时文件,将图片存储 |

| boolean inScaled | 设置为true时进行图片压缩,从inDensity到inTargetDensity |

| boolean inDither | 如果为true,解码器尝试抖动解码 |

| Bitmap.Config inPreferredConfig | 设置解码器这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes |

| String outMimeType | 设置解码图像 |

| boolean inPurgeable | 当存储Pixel的内存空间在系统内存不足时是否可以被回收 |

| boolean inInputShareable | inPurgeable为true情况下才生效,是否可以共享一个InputStream |

| boolean inPreferQualityOverSpeed | 为true则优先保证Bitmap质量其次是解码速度 |

| boolean inMutable | 配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段 |

| int inScreenDensity | 当前屏幕的像素密度 |

基本使用

try {

FileInputStream fis = new FileInputStream(filePath);

BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

// 设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和options.outHeight

BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);

float srcWidth = options.outWidth;

float srcHeight = options.outHeight;

int inSampleSize = 1;

if (srcHeight > height || srcWidth > width) {

if (srcWidth > srcHeight) {

inSampleSize = Math.round(srcHeight / height);

} else {

inSampleSize = Math.round(srcWidth / width);

}

}

options.inJustDecodeBounds = false;

options.inSampleSize = inSampleSize;

return BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);

} catch (Exception e) {

e.printStackTrace();

}

内存回收


if(bitmap != null && !bitmap.isRecycled()){

// 回收并且置为null

bitmap.recycle();

bitmap = null;

}

Bitmap 类的构造方法都是私有的,所以开发者不能直接 new 出一个 Bitmap 对象,只能通过 BitmapFactory 类的各种静态方法来实例化一个 Bitmap。仔细查看 BitmapFactory 的源代码可以看到,生成 Bitmap 对象最终都是通过 JNI 调用方式实现的。所以,加载 Bitmap 到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java 部分的,一部分是 C 部分的。这个 Bitmap 对象是由 Java 部分分配的,不用的时候系统就会自动回收了,但是那个对应的 C 可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用 recycle() 方法来释放 C 部分的内存。从 Bitmap 类的源代码也可以看到,recycle() 方法里也的确是调用了 JNI 方法了的。

屏幕适配

====

单位

  • dpi 每英寸像素数(dot per inch)

  • dp

密度无关像素 - 一种基于屏幕物理密度的抽象单元。 这些单位相对于 160 dpi 的屏幕,因此一个 dp 是 160 dpi 屏幕上的一个 px。 dp 与像素的比率将随着屏幕密度而变化,但不一定成正比。为不同设备的 UI 元素的实际大小提供了一致性。

  • sp

与比例无关的像素 - 这与 dp 单位类似,但它也可以通过用户的字体大小首选项进行缩放。建议在指定字体大小时使用此单位,以便根据屏幕密度和用户偏好调整它们。

dpi = px / inch

density = dpi / 160

dp = px / density

头条适配方案


private static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) {

final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();

if (sNoncompatDensity == 0) {

sNoncompatDensity = appDisplayMetrics.density;

sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;

// 监听字体切换

application.registerComponentCallbacks(new ComponentCallbacks() {

@Override

public void onConfigurationChanged(Configuration newConfig) {

if (newConfig != null && newConfig.fontScale > 0) {

sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;

}

}

@Override

public void onLowMemory() {

}

});

}

// 适配后的dpi将统一为360dpi

final float targetDensity = appDisplayMetrics.widthPixels / 360;

final float targetScaledDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);

final int targetDensityDpi = (int)(160 * targetDensity);

appDisplayMetrics.density = targetDensity;

appDisplayMetrics.scaledDensity = targetScaledDensity;

appDisplayMetrics.densityDpi = targetDensityDpi;

final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();

activityDisplayMetrics.density = targetDensity;

activityDisplayMetrics.scaledDensity = targetScaledDensity;

activityDisplayMetrics.densityDpi = targetDensityDpi

}

刘海屏适配


  • Android P 刘海屏适配方案

Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。要确定这些凹口屏幕区域是否存在及其位置,使用 getDisplayCutout() 函数。

| DisplayCutout 类方法 | 说明 |

| — | — |

| getBoundingRects() | 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形 |

| getSafeInsetLeft () | 返回安全区域距离屏幕左边的距离,单位是px |

| getSafeInsetRight () | 返回安全区域距离屏幕右边的距离,单位是px |

| getSafeInsetTop () | 返回安全区域距离屏幕顶部的距离,单位是px |

| getSafeInsetBottom() | 返回安全区域距离屏幕底部的距离,单位是px |

Android P 中 WindowManager.LayoutParams 新增了一个布局参数属性 layoutInDisplayCutoutMode:

| 模式 | 模式说明 |

| — | — |

| LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠。 |

| LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 该窗口决不允许与DisplayCutout区域重叠。 |

| LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。 |

  • Android P 之前的刘海屏适配

不同厂商的刘海屏适配方案不尽相同,需分别查阅各自的开发者文档。

Context

=======

Context 本身是一个抽象类,是对一系列系统服务接口的封装,包括:内部资源、包、类加载、I/O操作、权限、主线程、IPC 和组件启动等操作的管理。ContextImpl, Activity, Service, Application 这些都是 Context 的直接或间接子类, 关系如下:

ContextWrapper是代理Context的实现,简单地将其所有调用委托给另一个Context(mBase)。

Application、Activity、Service通过attach() 调用父类ContextWrapper的attachBaseContext(), 从而设置父类成员变量 mBase 为 ContextImpl 对象, ContextWrapper 的核心工作都是交给 mBase(ContextImpl) 来完成,这样可以子类化 Context 以修改行为而无需更改原始 Context。

实战系列

话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示


网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

tyDpi = targetDensityDpi

}

刘海屏适配


  • Android P 刘海屏适配方案

Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。要确定这些凹口屏幕区域是否存在及其位置,使用 getDisplayCutout() 函数。

| DisplayCutout 类方法 | 说明 |

| — | — |

| getBoundingRects() | 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形 |

| getSafeInsetLeft () | 返回安全区域距离屏幕左边的距离,单位是px |

| getSafeInsetRight () | 返回安全区域距离屏幕右边的距离,单位是px |

| getSafeInsetTop () | 返回安全区域距离屏幕顶部的距离,单位是px |

| getSafeInsetBottom() | 返回安全区域距离屏幕底部的距离,单位是px |

Android P 中 WindowManager.LayoutParams 新增了一个布局参数属性 layoutInDisplayCutoutMode:

| 模式 | 模式说明 |

| — | — |

| LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠。 |

| LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 该窗口决不允许与DisplayCutout区域重叠。 |

| LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。 |

  • Android P 之前的刘海屏适配

不同厂商的刘海屏适配方案不尽相同,需分别查阅各自的开发者文档。

Context

=======

Context 本身是一个抽象类,是对一系列系统服务接口的封装,包括:内部资源、包、类加载、I/O操作、权限、主线程、IPC 和组件启动等操作的管理。ContextImpl, Activity, Service, Application 这些都是 Context 的直接或间接子类, 关系如下:

ContextWrapper是代理Context的实现,简单地将其所有调用委托给另一个Context(mBase)。

Application、Activity、Service通过attach() 调用父类ContextWrapper的attachBaseContext(), 从而设置父类成员变量 mBase 为 ContextImpl 对象, ContextWrapper 的核心工作都是交给 mBase(ContextImpl) 来完成,这样可以子类化 Context 以修改行为而无需更改原始 Context。

实战系列

话不多说,Android实战系列集合都已经系统分类好,由于文章篇幅问题没法过多展示

[外链图片转存中…(img-tNL7B7mv-1714399603650)]
[外链图片转存中…(img-U4kS8GyB-1714399603650)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
移动安全系列教学下载共43份: 内容如下: Android安全Hook--35--Hook技术简介.pdf Android安全Hook--36--Xposed源码分析(一).pdf Android安全Hook--37--Xposed源码分析(二).pdf Android安全Hook--38--Xposed源码分析(三).pdf Android安全Hook--39--Xposed源码分析(四).pdf Android安全安全技术--16--Android系统安全(上).pdf Android安全安全技术--17--Android系统安全(下).pdf Android安全安全技术--18--ARM汇编语言.pdf Android安全安全技术--19--Small汇编语言.pdf Android安全安全技术--20--基础文件格式解析.pdf Android安全安全技术--21--Android应用安全防护技术.pdf Android安全安全技术--22--其他Android安全知识总结.pdf Android安全安全技术--34--Emulator模拟器的配置和ROOT(终极版).pdf Android安全应用逆向--23--反编译classes.dex文件.pdf Android安全应用逆向--24--使用Apktool解包并打包.pdf Android安全应用逆向--25--使用IDA静态分析so文件.pdf Android安全应用逆向--26--动态调试Smali源码.pdf Android安全应用逆向--27--使用IDA动态调试.so文件.pdf Android安全应用逆向--28--在JNI_onload函数处下断点避开针对IDA的反调试.pdf Android安全应用逆向--29--Small注入.pdf Android安全应用逆向--30--破解java层的签名校验.pdf Android安全应用逆向--31--破解NDK层的签名校验.pdf Android安全应用逆向--32--使用IDA Pro进行脱壳.pdf Android安全应用逆向--33--加密算法基础.pdf Android安全开发基础--1--开发基础常识.pdf Android安全开发基础--10--图形界面(UI)和碎片(Fragment)(上).pdf Android安全开发基础--11--图形界面(UI)和碎片(Fragment)(下).pdf Android安全开发基础--12--持久化技术.pdf Android安全开发基础--13--多媒体.pdf Android安全开发基础--14--其他理论知识.pdf Android安全开发基础--15--应用程序配置文件详解(AndroidManifest.xml).pdf Android安全开发基础--2--四大组件之活动(Activity).pdf Android安全开发基础--3--四大组件之服务 (Service).pdf Android安全开发基础--4--四大组件之广播(Broadcast Receiver).pdf Android安全开发基础--5--四大组件之内容提供器(Content Provider).pdf Android安全开发基础--6--进程间通信机制(IPC).pdf Android安全开发基础--7--JVM Dalvik ART虚拟机.pdf Android安全开发基础--8--Java本地接口(JNI).pdf Android安全开发基础--9--Android系统的启动过程分析.pdf Android安全软件壳--41--说说Android软件壳.pdf Android安全逆向工程--40--逆向工程知识技能补充篇.pdf 移动安全--42--MobSF-v3.0源代码分析【长文巨献】.pdf 移动安全--43--我设计的Java代码混淆解决方案.pdf

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值