Android面试题解答(结尾有彩蛋)

用于面试参考,不做深究。答案整理自互联网,也会加上我自己的理解。

说说Activity的启动流程

Activity启动有几种方式?一种是写一个startActivity,第二种是点击手机App,通过手机系统里的Launcher机制,启动App里默认的Activity。

IActivityManager IApplicationThread 是AIDL接口文件.

在API26中: ActivityManagerNative类被弃用,代理类ActivityManagerProxy已经被删除了。这里直接使用aidl的方式来通信了,不在使用远端服务的中间类了。而是直接使用IActivityManager.Stub.asInterface(b); 来获取AMS。

同样的ApplicationThreadNative和代理类ApplicationThreadProxy,改用IApplicationThread.Stub代替。

base/core/java/android/app/Instrumentation.java

//api 25
int result = ActivityManagerNative.getDefault() int result = ActivityManagerNative.getDefault()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
//api 26
 int result = ActivityManager.getService()//注意这一行
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);

在App进程中:
sytem_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求。
App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息。
主线程在收到Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法。

代码调用大致如下:

//ams中
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
    ...
    ////只有当系统启动完,或者app允许启动过程允许,则会true
    boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
    thread.bindApplication(...);
    if (normalMode) {
        if (mStackSupervisor.attachApplicationLocked(app)) {
            didSomething = true;
        }
    }
    ...
}
//ASS.attachApplicationLocked
boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
		...
                    try {
                        //真正的启动Activity
                        if (realStartActivityLocked(hr, app, true, true)) {
                            didSomething = true;
                        }
                    } catch (RemoteException e) {
                        throw e;
                    }
    if (!didSomething) {
        //启动Activity不成功,则确保有可见的Activity
        ensureActivitiesVisibleLocked(null, 0);
    }
    return didSomething;
}
//ASS.realStartActivityLocked
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException {
            ......
            app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                    System.identityHashCode(r), r.info,
                    // TODO: Have this take the merged configuration instead of separate global and
                    // override configs.
                    mergedConfiguration.getGlobalConfiguration(),
                    mergedConfiguration.getOverrideConfiguration(), r.compat,
                    r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                    r.persistentState, results, newIntents, !andResume,
                    mService.isNextTransitionForward(), profilerInfo);
}
// ApplicationThread.scheduleLaunchActivity
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
sendMessage(H.LAUNCH_ACTIVITY, r);
...
}

接着看下代码创建Activity与开始回调生命周期

ActivityThread.handleLaunchActivity

// 启动Activity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    // Make sure we are running with the most recent config.
    // 最终回调目标Activity的onConfigurationChanged()
    handleConfigurationChanged(null, null);
   ...
    // 首先调用performLaunchActivity方法将目标Activity组件启动起来,最终调用目标Activity的onCreate方法
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        ...
        // 最终回调目标Activity的onStart,onResume.
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
        if (!r.activity.mFinished && r.startsNotResumed) {
            ...
            performPauseActivityIfNeeded(r, reason);
            ...
    } else {
        ...
    }
}

ActivityThread.performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    Activity activity = null;
    try {
        // 将目标Activity的类文件加载到内存中,并创建一个实例。由于所有Activity组件都是从Activity类
        // 继承下来的,因此,我们就可以将前面创建的Activity组件保存在Activity对象activity中
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
    ...
    try {
        // 创建Application对象
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
         if (activity != null) {
            ...
            // 使用ContextImpl对象appContext和ActivityClientRecord对象r来初始化Activity对象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, window);
            ...
            int theme = r.activityInfo.getThemeResource();
            if (theme != 0) {
                activity.setTheme(theme);
            }
            activity.mCalled = false;
            // 调用成员变量mInstrumentation的callActivityOnCreate方法将Activity对象activity启动起来,
            // 会调用Activity的onCreate方法
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            ...
            if (!r.activity.mFinished) {
                activity.performStart();
                r.stopped = false;
            }
            if (!r.activity.mFinished) {
                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);
                }
            }
            ...
        }
    ...
    return activity;
}

理解 Instrumentation 类:
1.Instrumentation是用来监控程序与系统之间的交互操作的
2.mMainThread的类型为ActivityThread,是用来描述应用程序进程的,系统每当启动一个应用程序进程时,都会在它里面加载一个ActivityThread类实体,并且这个类实体会保存在每一个在该进程中启动的Activity组件的父类Activity的成员变量mMainThread中。
3.ActivityThread的成员函数getApplicationThread用来获取它内部一个类型为ApplicationThread对象作为参数传递给变量mInstrumentation的成员函数execStartActivity,以便将它传递给AMS,这样就可以通过ApplicationThread与Activity交流了。
4.Activity的成员变量mToken的类型为IBinder,它是Binder代理对象,指向AMS中一个类型为ActivityRecord对象,用来维护对应的Activity组件的运行状态以及信息。此处传入也是为了传递给AMS,这样AMS就可以得到Activity的详细信息了。

    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                // If this start is requesting a result, we can avoid making
                // the activity visible until the result is received.  Setting
                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                // activity hidden during this time, to avoid flickering.
                // This can only be done when a result is requested because
                // that guarantees we will get information back when the
                // activity is finished, no matter what happens to it.
                mStartedActivity = true;
            }
            cancelInputsAndStartExitTransition(options);
            // TODO Consider clearing/flushing other event sources and events for child windows.
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                // Note we want to go through this method for compatibility with
                // existing applications that may have overridden it.
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

总结上面两个问题
第一种:
1.Activity1调用startActivity,实际会调用Instrumentation类的execStartActivity方法,Instrumentation是系统用来监控Activity运行的一个类,Activity的整个生命周期都有它的影子。
2.通过跨进程的binder调用,进入到ActivityManagerService(AMS)中,其内部(ActivityStackSupervisor)会处理Activity栈。之后又通过跨进程调用进入到Activity2所在的进程中。
3.ApplicationThread是一个binder对象,其运行在binder线程池中,内部包含一个H类,该类继承于Handler。ApplicationThread将启动Activity2的信息通过H对象发送给主线程。
4.主线程拿到Activity2的信息后,调用Instrumentation类的newAcitivity方法,其内部通过ClassLoader创建Activity2实例。

第二种:
1.点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
2.system_server进程接收到请求后,向zygote进程发送创建进程的请求;
3.Zygote进程fork出新的子进程,即App进程;

在这里插入图片描述

说一说service

首先服务是一种即使用户未与应用交互也可在后台运行的组件。
1.分为前台服务和后台服务与绑定服务。
绑定服务指:当应用组件通过调用 bindService() 绑定到服务时,服务即处于绑定状态。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
2.启动服务与绑定服务可以同时运行。唯一的问题在于您是否实现一组回调方法:onStartCommand()(让组件启动服务)和 onBind()(实现服务绑定)
3.应用组件(如 Activity)可通过调用 startService() 方法并传递 Intent 对象(指定服务幷包含待使用服务的所有数据)来启动服务。服务会在 onStartCommand() 方法接收此 Intent。

讲讲Binder的原理

1.Binder是一种进程间通信(IPC)方式,Android常见的进程中通信方式有文件共享,Bundle,AIDL,Messenger,ContentProvider,Socket。其中AIDL,Messenger,ContentProvider都是基于Binder。Linux系统通过Binder驱动来管理Binder机制。
2.Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。
通信过程:
Binder 通信过程:
1.首先,一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
2.Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
3.Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

  • A 进程想要 B 进程中某个对象(object)是如何实现的呢?(Binder)
  1. 跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。
  2. 当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。
  3. 当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

额外的代码理解:

/**
 * 这个类用来定义服务端 RemoteService 具备什么样的能力
 */
public interface BookManager extends IInterface {

    void addBook(Book book) throws RemoteException;
}

public abstract class Stub extends Binder implements BookManager {

    ...

    public static BookManager asInterface(IBinder binder) {
        if (binder == null)
            return null;
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof BookManager)
            return (BookManager) iin;
        return new Proxy(binder);
    }

    ...

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {

            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;

            case TRANSAVTION_addBook:
                data.enforceInterface(DESCRIPTOR);
                Book arg0 = null;
                if (data.readInt() != 0) {
                    arg0 = Book.CREATOR.createFromParcel(data);
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;

        }
        return super.onTransact(code, data, reply, flags);
    }

    ...
}

public class Proxy implements BookManager {

    ...

    public Proxy(IBinder remote) {
        this.remote = remote;
    }

    @Override
    public void addBook(Book book) throws RemoteException {

        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if (book != null) {
                data.writeInt(1);
                book.writeToParcel(data, 0);
            } else {
                data.writeInt(0);
            }
            remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
            replay.readException();
        } finally {
            replay.recycle();
            data.recycle();
        }
    }

    ...
}

Handler的实现方式

文章的链接

RecyclerView的缓存模式

文章的链接

View的绘制流程

View的整个绘制流程可以分为以下三个阶段:
measure: 判断是否需要重新计算View的大小,需要的话则计算。
测量表示值:
1.EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize);
2.AT_MOST: 子View的大小不得超过SpecSize;
3.UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部。

//这段代码表示了
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  // spec为父View的MeasureSpec
  // padding为父View在相应方向的已用尺寸加上父View的padding和子View的margin
  // childDimension为子View的LayoutParams的值
  int specMode = MeasureSpec.getMode(spec);
  int specSize = MeasureSpec.getSize(spec);

  // 现在size的值为父View相应方向上的可用大小
  int size = Math.max(0, specSize - padding);

  int resultSize = 0;
  int resultMode = 0;

  switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
      if (childDimension >= 0) {
        // 表示子View的LayoutParams指定了具体大小值(xx dp)
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // 子View想和父View一样大
        resultSize = size;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // 子View想自己决定其尺寸,但不能比父View大 
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      }
      break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
      if (childDimension >= 0) {
        // 子View指定了具体大小
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // 子View想跟父View一样大,但是父View的大小未固定下来
        // 所以指定约束子View不能比父View大
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // 子View想要自己决定尺寸,但不能比父View大
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      }
      break;

      . . .
  }

  //noinspection ResourceType
  return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

layout: 判断是否需要重新计算View的位置,需要的话则计算。
draw: 判断是否需要重新绘制View,需要的话则重绘制。

Android的事件分发流程

概念
Android的TouchEvent通常包含三个动作,ACTION_DOWN,ACTION_MOVEACTION_UP。发出的顺序是DOWN->MOVE->MOVE->…->UP(注意MOVE事件是否能够被触发取决于操作手势里面是否包含了移动的动作)。
简单的流程
1.消息分发流程,从上到下,从父到子:Activity->ViewGroup1->ViewGroup1的子ViewGroup2->…->Target View
2.消息响应流程,从下到上,从子到父:Target View->…->ViewGroup1的子ViewGroup2->ViewGroup1->Activity
具体的分析
public boolean dispatchTouchEvent(MotionEvent ev);
事件分发处理函数,通常会在Activity层根据UI的显示情况,把事件传递给相应的ViewGroup。
public boolean onInterceptTouchEvent(MotionEvent ev);
对分发的事件进行拦截,注意拦截ACION_DOWN与其他ACTION的差异
第1种情况:如果ACTION_DOWN的事件没有被拦截,顺利找到了TargetView,那么后续的MOVE与UP都能够下发。如果后续的MOVE与UP下发时还有继续拦截的话,事件只能传递到拦截层,并且发出ACTION_CANCEL。
第2种情况:如果ACITON_DOWN的事件下发时被拦截,导致没有找到TargetView,那么后续的MOVE与UP都无法向下派发了,在Activity层就终止了传递。
public boolean onTouchEvent(MotionEvent ev);
响应处理函数,如果有设置对应listener的话,这里还会与onTouch,onClick,onLongClick有关联。具体执行顺序是onTouch()->onTouchEvent()->onClick()->onLongClick()。是否能够顺序执行,取决于每个方法的返回值是true还是false。

  • 如何优化View的绘制
    大方向:
    1.减少 View 层级。这样会加快 View 的循环遍历过程。
    2.去除不必要的背景。由于 在 draw 的步骤中,会单独绘制背景。因此去除不必要的背景会加快 View 的绘制。
    3.尽可能少的使用 margin、padding。在测量和布局的过程中,对有 margin 和 padding 的 View 会进行单独的处理步骤,这样会花费时间。我们可以在父 View 中设置 margin 和 padding,从而避免在子 View 中每个单独设置和配置。
    具体的方向:
    1.在onDraw()方法是同步的主线程的所以尽量不要用for循环,可以用path去装载要绘制的点,然后drawPath()来代替for循环。
    2.减少局部对象的创建。
    3.当内存不足时或者对象重创建的时候我们自己的view 保存自己的属性 ,onSaveInstanceState()与onRestoreInstanceState(Parcelable state)。

解释一下数据库的事务

理论:
事务是恢复和并发控制的基本单位。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
1.原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
2.一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
3.隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
4.持久性(durability)。指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

//最后使用sqlite3查看数据库person表中的数据,转账没有成功,张三和王五的钱都没有变化
public void testTransaction() throws Exception {
        PersonSQLiteOpenHelper helper = new PersonSQLiteOpenHelper(getContext());
        SQLiteDatabase db = helper.getReadableDatabase();
        // 开始数据库的事务
        db.beginTransaction();
        try {
            db.execSQL("update person set account=account-1000 where name=?",
                    new Object[] { "zhangsan" });
            
            int i = 2/0; //制造异常测试事务操作

            db.execSQL("update person set account=account+1000 where name=?",
                    new Object[] { "wangwu" });

            // 标记数据库事务执行成功
            db.setTransactionSuccessful();

        } catch (Exception e) {

        } finally {
            db.endTransaction();
            db.close();

        }

    }

说一下Android组件化开发

链接

说说clickable enable 在事件分发里面的作用

clickable 与 enable的源码解析:

//只有clickable为true的时候,才会执行下面的手势状态
 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    //...
                      if (!post(mPerformClick)) {
                        performClickInternal();
                     }
                    //...
                break;
            }

// 如果clickable之前设置了false 那么重新设置监听之后,会重新置为true。
public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
//enable
   public boolean dispatchTouchEvent(MotionEvent event) {

        ......

        boolean result = false;

        ......

        if (onFilterTouchEventForSecurity(event)) {
        //如果设置了false 那么就无法滑动thumb
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            //如果enable设置了false 后面的ontouch不会执行短路了
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			//result 为false的时候,会进入onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ......

        return result;
    } 
//虽然进去了onTouchEvent 但是也会直接return出去 返回值取决于clickable的设置
public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

讲一讲Android的ANR

无论是四大组件或者进程等只要发生ANR,最终都会调AMS.appNotResponding()方法.
实现的条件:
1.Service Timeout:比如前台服务在20s内未执行完成;
2.BroadcastQueue Timeout:比如前台广播在10s内未执行完成
3.InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
如何分析:
1.分析这个下面的文件/data/anr/traces
2.当前发生ANR的进程,system_server进程以及所有persistent进程
audioserver, cameraserver, mediaserver, surfaceflinger等重要的native进程
CPU使用率排名前5的进程
2.将发生ANR的reason以及CPU使用情况信息输出到main log
将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录
3.对用户可感知的进程则弹出ANR对话框告知用户,对用户不可感知的进程发生ANR则直接杀掉

说一下你如何应对内存泄露,你是怎么检测的

概念:
内存空间使用完毕之后未回收,会出现oom
出现的场景:
1.静态的变量,如view视图
2.内部类持有静态变量的引用,内部类的优势就是方遍的访问外部类,但是内部类会持有外部类实例的强引用。
3.handler在activity 的使用,使用匿名的handler与匿名的runnable对象来执行任务,极可能发生内存泄漏。
4.线程的使用
5.引用的Context对象
6.cursor未关闭
分析:
可以使用 adb shell dumpsys meminfo 来查看内存使用状况
使用lint检测
使用LeakCanary检测
在activity的生命周期里面即使去销毁定时任务,handler的任务 ,按照场景使用WeakReference来控制对象的内存释放,

说一下Android 7.0,8.0,9.0,10对于应用层开发大的变动有哪些?

Android10主要变动:
1.如果应用以 Android 10 或更高版本为目标平台并使用涉及全屏 intent 的通知,则必须在应用的清单文件中请求 USE_FULL_SCREEN_INTENT 权限。这是普通权限,因此,系统会自动为请求权限的应用授予此权限。
2.如果用户从屏幕边缘向内滑动,系统会将该手势解读为“返回”导航,除非应用针对屏幕的相应部分明确替换该手势。
为了确保您的应用与手势导航兼容,您需要将应用内容扩展到屏幕边缘,并适当地处理存在冲突的手势。
Android9主要变动:
1.后台对传感器的访问受限
2.限制访问通话记录
3.限制访问 Wi-Fi 位置和连接信息;
Android8主要变动:
1.Android 8.0(API 级别 26)添加了对通知渠道的支持(NotificationChannels),即应用可以将其通知整理划分到不同的主题类别中。每种类别都有自己的提醒类型,用户可以根据自己的兴趣选择性地启用或停用这些类别。
2.在 Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service。 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 如果应用在此时间限制内未调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR
3.如果您的清单为显式广播声明了接收器,您必须予以替换。 可能的解决方法包括:通过调用 Context.registerReceiver() 而不是在清单中声明接收器的方式在运行时创建接收器。
Android7.0主要变动
1.多窗口的支持,通知的变更
2.优化了后台,同时删除了三个常用隐式广播 — CONNECTIVITY_ACTIONACTION_NEW_PICTUREACTION_NEW_VIDEO
3.私有文件的文件权限不应再由所有者放宽,为使用 MODE_WORLD_READABLE 和/或 MODE_WORLD_WRITEABLE 而进行的此类尝试将触发 SecurityException
4.传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider
Android6.0主要变动:
1.权限模式。对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。
2.Android 6.0 版移除了对 Apache HTTP 客户端的支持。
3.修改adb shell dumpsys notification --noredact 命令打印输出 notification 对象中的文本。

想知道更多更详细的版本变更点击这里

讲一讲aidl的具体使用

这里就不说具体的代码了,上面已经介绍了。补充一点知识:
1.aidl支持7种数据类型(除去short类型),String,CharSequence,实现了Parcelable接口的数据类型,List类型,Map类型。
2.in 表示数据只能由客户端流向服务端。
3.out 表示数据只能由服务端流向客户端。
4.inout 则表示数据可在服务端与客户端之间双向流通
5.如果AIDL方法接口的参数值类型是:基本数据类型(除去short类型)、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in

原理:
AIDL 使用 Binder 内核驱动程序进行调用。当您发出调用时,系统会将方法标识符和所有对象打包到某个缓冲区中,然后将其复制到某个远程进程,该进程中有一个 Binder 线程正在等待读取数据。Binder 线程收到某个事务的数据后,该线程会在本地进程中查找原生存根对象,然后此类会解压缩数据并调用本地接口对象。此本地接口对象正是服务器进程所创建和注册的对象。当在同一进程和同一后端中进行调用时,不存在代理对象,因此直接调用即可,无需执行任何打包或解压缩操作。

如何优化app的启动

链接在这里

讲讲你对MVP模式的理解

1.就是 Activity 或者 Fragment 直接与数据层交互,activity 通过 apiProvider 进行网络访问,或者通过 CacheProvider 读取本地缓存,然后在返回或者回调里对 Activity 的界面进行响应刷新。
2.视图层(View):
负责绘制UI元素、与用户进行交互,对应于XML、Activity、Fragment。
3.模型层(Model):
负责存储、检索、操纵数据,一般包含网络请求,数据库处理,I/O流。
4.控制层(Presenter):
Presenter是整个MVP体系的控制中心,作为View与Model交互的中间纽带,处理View于Model间的交互和业务逻辑。

为什么okHttp3 好用呢?

1.支持HTTP2/SPDY(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验)
2.socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数
3.基于Headers的缓存策略减少重复的网络请求
4.拥有Interceptors轻松处理请求与响应(自动处理GZip压缩)

书面回答,意义不大。可以查阅源码分析。

retrofit的动态代理中是如何处理接口返回类型的(因为接口申明的泛型在运行时会被擦除)

这个不知道,有知道的可以留言告知。


乐观锁与悲观锁

概念与使用场景:
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
代码例子:

// ------------------------- 悲观锁的调用方式 -------------------------
// synchronized
public synchronized void testMethod() {
	// 操作同步资源
}
// ReentrantLock
private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用的是同一个锁
public void modifyPublicResources() {
	lock.lock();
	// 操作同步资源
	lock.unlock();
}

// ------------------------- 乐观锁的调用方式 -------------------------
private AtomicInteger atomicInteger = new AtomicInteger();  // 需要保证多个线程使用的是同一个AtomicInteger
atomicInteger.incrementAndGet(); //执行自增1

想了解更多请点击

单例模式的缺点

这个问题可能是要讲清楚各种单例写法的优缺点。
1.明确饿汉式写法的优缺点
类加载的时候,就会初始化。不管需不需要,消耗内存,但是线程安全,因为一个类在一个ClassLoader中只会被加载一次,单例模式的mInstance在加载时就已经初始化了,这可以确定对象的唯一性。同时也保证了在多线程并发情况下获取到的对象是唯一的。
2.明确懒汉式加载的优缺点:
可以实现懒加载,根据需要实例化代码,但是多数情况下线程不是安全的。

代码举例:列出了Android开发中比较常用的方式

//饿汉式
public class SingletonHungry {
    private static SingletonHungry mInstance = new SingletonHungry();
    private SingletonHungry() {
    }
    public static SingletonHungry getInstance() {
        return mInstance;
    }
 }
//懒汉式非线程安全
public class SingletonLazy {
    private static SingletonLazy mInstance;
    private SingletonLazy() {
    }
    public static SingletonLazy getInstanceUnLocked() {
        if (mInstance == null) {
            mInstance = new SingletonLazy();
        }
        return mInstance;
    }
}
//懒汉式线程安全,但是损耗性能
public class SingletonLazy {
//关键字volatile 实现原子性
    private volatile static SingletonLazy mInstance;
    private SingletonLazy() {
    }
    public static SingletonLazy getInstance() {
    //第一次检测
        if (mInstance == null) {
        //加锁
            synchronized (SingletonLazy.class) {
            //第二次检测
                if (mInstance == null) {
                //new 对象
                    mInstance = new SingletonLazy();
                }
            }
        }
        return mInstance;
    }
 }

2.面对项目中大量的单例模式,我们可以使用生成器来让单例少一些。

static class SingletonManager {
        private static Map<String, Object> objectMap = new HashMap<>();

        private SingletonManager() {
        }

        public static void registerService(String key, Object ins) {
            if (!objectMap.containsKey(key)) {
                objectMap.put(key, ins);
            }
        }

        public static Object getService(String key) {
            return objectMap.get(key);
        }

    }

线程死锁出现的条件,如何避免

java 死锁产生的四个必要条件:
1.互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2.不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4.循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

Java线程池的运用

   public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
//corePoolSize 核心池的大小
//maximumPoolSize 线程池最大线程数
//keepAliveTime 表示线程没有任务执行时最多保持多久时间会终止
//unit 时间单位
// workQueue ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue 阻塞队列

说一下Java二维数组

可以看这个链接

TCP三次握手与四次挥手

三次握手
首先Client端发送连接请求报文(SYN = 1,seq = client_isn)
server接受连接后回复ACK报文,并为这次连接分配资源。(SYN = 1,seq=client_isn ack = client_isn +1 )
Client端接收到ACK报文后也向Server端发送ack报文,并分配资源。(SYN = 0,seq=client_isn ack = client_isn +1 )
四次挥手:
第一次:Client端发起中断连接请求,发送FIN报文。
第二次:Server端接到FIN报文后,会把剩下得数据连同ACK一同发送给Client端。
第三次:Client端就进入FIN_WAIT状态,等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文。
Client端收到FIN报文后,发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。
Server端收到ACK后,断开连接。
第四次:Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,Client端也可以关闭连接了

在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.
SYN表示建立连接,
FIN表示关闭连接,
ACK表示响应,
PSH表示有 DATA数据传输,
RST表示连接重置。

蛋:
上面是我面试一家2线不知名网络公司的面试题。(浙江宁波的某网络公司)今年成立的网络公司要14个人。面了1小时,很多问题我都不知道该怎么回答。对方也不给我解答任何一个面试问题,面完了人力资源也不会给你任何答复(毁三观的面试),这种公司摆明了就是完成招聘任务而设套,大家要小心这种套子公司。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章