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

Posted 让开,我要吃人了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android学习指南 — Android基础知识汇总相关的知识,希望对你有一定的参考价值。

本次的学习指南系列将分为三个模块

  • 第一 Java知识点汇总
  • 第二 android基础知识点汇总(一)(二)
  • 第三 Android进阶知识点汇总

后续的NDK、跨平台、底层源码等技术,也会抽空给大家整理一下知识点,如果喜欢的话,希望大家给个关注,点个赞呗,个人主页简介有联系方式,可找我拿PDF版本的学习指南。

怎么联系我:Android.md · master · 让开,我要吃人了 / Android · CODE CHINACODE CHINA——开源代码托管平台,独立第三方开源社区,Git/Github/Gitlabhttps://codechina.csdn.net/weixin_55362248/android/-/blob/master/Android.md

Activity

生命周期

  • Activity A 启动另一个Activity B,回调如下:
    Activity A 的onPause() → Activity B的onCreate() → onStart() → onResume() → Activity A的onStop();如果B是透明主题又或则是个DialogActivity,则不会回调A的onStop;
  • 使用onSaveInstanceState()保存简单,轻量级的UI状态
lateinit var textView: TextView
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)
    setContentView(R.layout.activity_main)
    textView = findViewById(R.id.text_view)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    super.onSaveInstanceState(outState)
}

启动模式

LaunchMode说明
standard系统在启动它的任务中创建 activity 的新实例
singleTop如果activity的实例已存在于当前任务的顶部,则系统通过调用其onNewIntent(),否则会创建新实例
singleTask系统创建新 task 并在 task 的根目录下实例化 activity。但如果 activity 的实例已存在于单独的任务中,则调用其 onNewIntent() 方法,其上面的实例会被移除栈。一次只能存在一个 activity 实例
singleInstance相同 singleTask,activity始终是其task的唯一成员; 任何由此开始的activity 都在一个单独的 task 中打开

启动过程

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        //step 1: 创建LoadedApk对象
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }
    ... //component初始化过程

    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    //step 2: 创建Activity对象
    Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    ...

    //step 3: 创建Application对象
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);

    if (activity != null) {
        //step 4: 创建ContextImpl对象
        Context appContext = createBaseContextForActivity(r, activity);
        CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
        Configuration config = new Configuration(mCompatConfiguration);
        //step5: 将Application/ContextImpl都attach到Activity对象
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.referrer, r.voiceInteractor);

        ...
        int theme = r.activityInfo.getThemeResource();
        if (theme != 0) {
            activity.setTheme(theme);
        }

        activity.mCalled = false;
        if (r.isPersistable()) {
            //step 6: 执行回调onCreate
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }

        r.activity = activity;
        r.stopped = true;
        if (!r.activity.mFinished) {
            activity.performStart(); //执行回调onStart
            r.stopped = false;
        }
        if (!r.activity.mFinished) {
            //执行回调onRestoreInstanceState
            if (r.isPersistable()) {
                if (r.state != null || r.persistentState != null) {
                    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 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务

启用前台服务

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
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/parentSpecModeEXACTLYAT_MOST
dp/pxEXACTLY(childSize)EXACTLY(childSize)
match_parentEXACTLY(childSize)AT_MOST(parentSize)
wrap_contentAT_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_ADJ16一般指将要会缓存进程,无法获取确定值
CACHED_APP_MAX_ADJ15不可见进程的adj最大值
CACHED_APP_MIN_ADJ9不可见进程的adj最小值
SERVICE_B_AD8B List 中的 Service(较老的、使用可能性更小)
PREVIOUS_APP_ADJ7上一个App的进程(往往通过按返回键)
HOME_APP_ADJ6Home进程
SERVICE_ADJ5服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ4后台的重量级进程,system/rootdir/init.rc 文件中设置
BACKUP_APP_ADJ3备份进程
PERCEPTIBLE_APP_ADJ2可感知进程,比如后台音乐播放
VISIBLE_APP_ADJ1可见进程(Visible process)
FOREGROUND_APP_ADJ0前台进程(Foreground process)
PERSISTENT_SERVICE_ADJ-11关联着系统或persistent进程
PERSISTENT_PROC_ADJ-12系统 persistent 进程,比如telephony
SYSTEM_ADJ-16系统进程
NATIVE_ADJ-17native进程(不被系统管理)

进程被杀情况

进程保活方案

  • 开启一个像素的 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<User> CREATOR = new Creator<User>() {
        @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;
}
方法含义
DESCRIPTORBinder 的唯一标识,一般用当前的 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对应着一个 Activity1~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

入门篇Android学习笔记——项目结构及相关基础知识

Android 学习之路--android基础

新手刚刚开始学习android,求推荐android开发入门教程

Android:Kotlin详细入门学习指南-函数-基础语法

Android:Kotlin详细入门学习指南-高阶函数-基础语法

Android:Kotlin详细入门学习指南-高阶函数-基础语法