Fork me on GitHub

Android组件

Activity和Window

视图类结构
视图层次
加载流程

Activity启动流程

app启动流程

启动模式

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

通知

通知结构

Intent标志

使用Intent标志 说明
FLAG_ACTIVITY_NEW_TASK 同 singleTask
FLAG_ACTIVITY_SINGLE_TOP 同 singleTop
FLAG_ACTIVITY_CLEAR_TOP 如果正在启动的 activity 已在当前 task中 运行,则不会启动该activity 的新实例,而是销毁其上的 activity,并调用其 onNewIntent()

启动流程

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;
}

启用前台服务

<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);

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

image
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 内容上边缘在竖直方向的距离。scrollToscrollBy 只能改变 View 内容的位置而不能改变 View 在布局中的位置。

  • 使用动画
    操作简单,主要适用于没有交互的 View 和实现复杂的动画效果。
  • 改变布局参数
    操作稍微复杂,适用于有交互的 View.
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
view.requestLayout();
//或者 view.setLayoutParams(params);
posted @ 2021-06-01 15:42  KevinAt2022  阅读(72)  评论(0)    收藏  举报