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小時,很多問題我都不知道該怎麼回答。對方也不給我解答任何一個面試問題,面完了人力資源也不會給你任何答覆(毀三觀的面試),這種公司擺明了就是完成招聘任務而設套,大家要小心這種套子公司。

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