android開發藝術探索(隨記)

1. activiy的生命周期和啟動模式

  1. ActivityA到ActivityB的跳轉生命周期的順序是什么呢?
    ActivityA-->onPause
    ActivityB-->onCreate
    ActivityB-->onStart
    ActivityB-->onResume
    ActivityA-->onStop
  2. 為什么先去執行A的onPause方法呢?
    回到源碼去看,在ActivityStack的resumeTopActivityInnerLocked方法中
// We need to start pausing the current activity so the top one can be resumed...
        final boolean dontWaitForPause = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
        boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, dontWaitForPause);
        if (mResumedActivity != null) {
            if (DEBUG_STATES) Slog.d(TAG_STATES,
                    "resumeTopActivityLocked: Pausing " + mResumedActivity);
            pausing |= startPausingLocked(userLeaving, false, next, dontWaitForPause);
        }

我們可以看到注解寫的很詳細了,需要把當前的activity pause掉,才能讓正在棧頂的新的activity啟動。

  1. activity遇到異常退出的保存流程
    由Activity去調用onSaveInstanceState方法,委托window去保存數據,window再委托上面的頂級容器(一般位DecorView)去保存數據,然后頂層容器去一層一層往下通知它的子控件,分別進行狀態保存。
    我們在Activity的onSaveInstanceState方法中可以看到:
protected void onSaveInstanceState(Bundle outState) {
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        getApplication().dispatchActivitySaveInstanceState(this, outState);
    }

mWindow.saveHierarchyState()將目標指向了Window的實現類PhoneWindow

/** {@inheritDoc} */
    @Override
    public Bundle saveHierarchyState() {
        Bundle outState = new Bundle();
        if (mContentParent == null) {
            return outState;
        }

        SparseArray<Parcelable> states = new SparseArray<Parcelable>();
        mContentParent.saveHierarchyState(states);
        outState.putSparseParcelableArray(VIEWS_TAG, states);

我們看到這里有個mContentParent.saveHierarchyState(states);,mContentParent我們可以找到是個ViewGroup,繼續找他的初始化

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

mContentParent = generateLayout(mDecor);很明顯了,就是之前上面說的DecorView就是最頂層的ViewGroup,然后就是調用saveHierarchyState進行保存狀態的上層往下層傳遞。

  1. 系統什么時候會去調onSaveInstanceState方法
    只有出現非事件驅動導致的activity銷毀,即用戶不知道這個activity竟然退出了的時候會去調用onSaveInstanceState,用戶如果想要去讓activity銷毀的話,系統是不會去調用這個方法的
  2. 如果一個進程中沒有四大組件了,那么這個進程很快就會被殺死,如果為了保證這個進程在后臺可以一直運行,那么就把后臺的工作放在service中,來保證這個進程一直存在。
  3. 如何用adb命令打印activity的任務棧:
    adb shell dumpsys activity
    然后我們可以檢索running activities 這一塊,找到運行的activity棧,我們可以看到其中有一個TaskRecord:
TaskRecord{d7d703 #6307 A=com.miui.home U=0 sz=1}
        Run #0: ActivityRecord{1b29801 u0 com.miui.home/.launcher.Launcher t6307}

根據包名我們知道這個activity就是桌面(由于用的是小米,所以你懂得)

  1. 在androidManifest文件中是如何定義一個activity的目標任務棧的:

<activity
android:name=".ui.information.InformationActivity"
android:configChanges="orientation|keyboardHidden|navigation"
android:screenOrientation="portrait"
android:taskAffinity="com.ding.hello"/>

通過taskAffinity的關鍵字,默認的taskAffinity是跟著application走的,是默認的包名,
1. 從修改任務棧的角度再去分析launchMode
  首先標準模式就是在當前這個activity所在的棧中去創建一個新的activity并且把它放到棧定,
  singleTop模式是是在當前activity所在的棧中去找有沒有這個activity有的話就把它移動到棧的頂部
  singleTask模式是在目標棧(記住此處是目標棧,如果沒有在清單文件中聲明taskAffinity的話,默認的就是包名棧)中找是否有目標activity的實例,如果有就把activity推到頂部,以上的activity都出棧
  singleInstance是單獨創建一個任務棧來保存啟動這個新的activity
  記住,當前的activity所在的任務棧叫做前臺任務棧,其他的任務棧都會被挪到后臺,當我們按返回鍵的時候會先去清空當前任務棧中的activity,當前臺任務棧清空完畢被回收掉了之后,會把后臺的任務棧啟動起來,而不斷的返回直到沒有用戶任務棧了之后,我們就會回到桌面這個任務棧,就是上面說到的launcher任務棧。
1. intenfilter的匹配規則直到多少?
     -  action
    action區分大小寫,只要action與intentfilter中的任意一個action完全相同那么就表示action匹配成功,如果intent跳轉的時候沒有給出action,那么就無法完成匹配。
     -  category
    即使不給出category依然可以進行匹配,因為不管怎么樣,我們只要在activity中去增加intentFilter肯定要加上一個category

<category android:name="android.intent.category.DEFAULT"/>

而如果我們在intent隱式跳轉中沒有添加category的話,系統會幫我們默認的把這個DEFAULT加進去。
如果我們添加了多個category,那么這些category必須要在intentfilter中的category都有才行,只要有一個不存在,那么就不能匹配。

     -  data
需要都匹配,通過intent.setDataAndType去設置它的匹配URI和mimeType。
1. 有哪兩個方法去查詢匹配的隱式activity?
PackageManager.java的

public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags);

后面的flag我們一般傳MATCH_DEFAULT_ONLY,這個參數表示,在清單文件中聲明了default這個category的才行,因為只有聲明了default才能被隱式調起,只會返回最符合要求的activity信息
Intent.java中的方法

public ComponentName resolveActivity(PackageManager pm) {

其實也是一個意思
還有一個是PackageManager的

public abstract List<ResolveInfo> queryIntentActivities(Intent intent, @ResolveInfoFlags int flags);

返回的是符合要求的所有的activities的集合

-----------
#2. IPC機制
1. android清單文件中如何指定運行進程?
在清單文件中,相應的四大組件中寫上android:process

android:process=":remote"
android:process="org.ding.example.remote"

默認的進程名是當前的包名,:remote則是當前包名+:remote, 后者就是直接拿來作為新的進程的名字
1. 打印當前進程列表或者制定的進程?
adb shell ps   答應當前進程列表
adb shell ps | grep org.ding.example 打印列表并檢索包含org.ding.example進程
打印出來之后,我們看到比如如下的結果:

adb shell ps -t | grep com.example
u0_a349 13180 667 1634392 66924 SyS_epoll_ 0000000000 S com.example.dingsigang.myapplication

我們可以通過adb shell ps -t | grep u0_a349,去把我們應用下面的所有的線程都打印出來:

u0_a349 13180 667 1634392 66924 SyS_epoll_ 0000000000 S com.example.dingsigang.myapplication
u0_a349 13185 13180 1634392 66924 do_sigtime 0000000000 S Signal Catcher
u0_a349 13186 13180 1634392 66924 poll_sched 0000000000 S JDWP
u0_a349 13187 13180 1634392 66924 futex_wait 0000000000 S ReferenceQueueD
u0_a349 13188 13180 1634392 66924 futex_wait 0000000000 S FinalizerDaemon
u0_a349 13189 13180 1634392 66924 futex_wait 0000000000 S FinalizerWatchd
u0_a349 13190 13180 1634392 66924 futex_wait 0000000000 S HeapTaskDaemon
u0_a349 13191 13180 1634392 66924 binder_thr 0000000000 S Binder_1
u0_a349 13192 13180 1634392 66924 binder_thr 0000000000 S Binder_2
u0_a349 13206 13180 1634392 66924 SyS_epoll_ 0000000000 S RenderThread
u0_a349 13207 13180 1634392 66924 inet_csk_a 0000000000 S Thread-881
u0_a349 13208 13180 1634392 66924 futex_wait 0000000000 S hwuiTask1
u0_a349 13209 13180 1634392 66924 futex_wait 0000000000 S hwuiTask2

1. 為什么進程要使用加冒號?
使用冒號說明這個進程是當前應用的私有進程,其他應用的組件是不可以跑到這個進程里來的,而沒有冒號的進程是可以共享的
1. 使用多進程了之后會碰到哪些問題?
     -  每一個進程都會擁有并維護自己的一套數據,或導致靜態變量和單例模式完全失效
     -  每個進程鎖住的對象都是自己進程維護的對象,會導致線程同步機制失效
     -  由于ShareParference底層使用的是XML的讀寫,會導致SP的可靠性下降
     -  每一次新起一個進程都會去創建一個新的application
1. Serializable的SerialVersionUID有什么用?
  不寫也可以進行序列化和反序列化,但是寫上了之后可以大大降低反序列話的失敗率,因為反序列需要去驗證前后的UID是否發生了變化
1. Binder類的onTransact方法,如果return 的true說明請求成功,如果返回的是false說明請求失敗,我們可以再次做權限驗證。
1. 如何給binder設置死亡代理?
IBinder里面有個內部接口

/**
* Interface for receiving a callback when the process hosting an IBinder
* has gone away.
*
* @see #linkToDeath
*/
public interface DeathRecipient {
public void binderDied();
}

當遠程服務被kill掉了之后,只要我們為我們的binder注冊了我們自己定義的DeathRecipient就可以回調到binderDied方法,我們可以在里面為我們的binder解綁死亡監聽。
綁定死亡監聽是用的是binder.linkToDeath,解綁使用的是unlinkToDeath方法
1. intent進程間通信解決無法用bundle傳遞的數據?
我們平時使用的比較多的就是intent啟動activity,然后通過intent攜帶數據進行傳遞,但是這個基本都是在一個進程中的,當我們在清單文件對activity設置了android:process之后就相當于用intent啟動了另一個進程,并且傳遞了數據,所以intent也是可以作為跨進程傳遞數據的喲。
那么回到問題,我們可以起B進程的一個service,然后把數據交給service,進程A可以直接從自己的進程里面直接去取這個service,并取出數據。
1. 文件共享這種進程間通信的方式需要區注意什么?
由于文件涉及到多線程操作的問題,只適合使用在多進程之間數據同步要求不高的環境,盡量妥善的去處理并發讀寫的問題。因為我們知道多進程操作的時候僅僅通過上鎖是解決不了問題的。
同樣的,SP也是一個進程間通信的方式,如果高并發的SP也是會造成相當大的丟失率的。
1. 知道Messenger進行進程間通信嗎?
先不去具體的分析,先看下使如何使用Messenger進行進程間通信的,先看service類

public class MessengerService extends Service {

private static class mHandle extends Handler{
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.e("xxxxxxxxx", "1111111");
    }
}

private Messenger messenger = new Messenger(new mHandle());

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return messenger.getBinder();
}

}

service在清單文件中的定義:

<service
android:name=".MessengerService"
android:process=":remote" />

然后在activity中啟用這個service

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
}

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Messenger messenger = new Messenger(service);
        try {
            messenger.send(Message.obtain());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};
我們看到Messenger也起到了線程間數據傳遞的作用,主要就是一下的幾個步驟:
      -  首先在service中定義一個messenger,傳入一個handler,這個handler的looper跟這個service的進程綁定
      -  復寫onBind方法(onBind的意思是當客戶端跟服務端連接上了之后,服務端所要返回給客戶端使用的IBinder對象),把messenger.getBinder返回出去。
      -  在activity中定義ServiceConnection對象,當連接上了之后,可以獲得IBinder接口對象,然后傳給Messenger進行直接使用,Messenger通過sendMessenge的方法可以直接讓服務里的handler直接收到message。
1. 那么messenger到底是一個什么機制呢?
   我們先去點開messenger類,把上面我們涉及到的三個主要的方法抽出來看一下:

public final class Messenger implements Parcelable {
private final IMessenger mTarget;
public Messenger(Handler target) { mTarget = target.getIMessenger();}
public void send(Message message) throws RemoteException {
mTarget.send(message);
}
public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target);}
}

原來也是用的binder。。。。我們再往深處看,首先是handler的getIMessenger方法:

final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl();
return mMessenger;
}
}

我們看到單例模式維護了一個MessengerImpl的單例,繼續看

private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
msg.sendingUid = Binder.getCallingUid();
Handler.this.sendMessage(msg);
}
}

很明顯了,就是binder機制,通過在進程A中構建一個Messenger,然后去send一個message,實際上就是通過進程B的handler去send一個message。
binder傳遞的東西,如果我們去看aidl轉化的binder類的源碼的話,會看到都會對數據進行parcel轉化,但是我們看到在IMessenger類中:

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_send: {
data.enforceInterface(DESCRIPTOR);
android.os.Message _arg0;
if ((0 != data.readInt())) {
_arg0 = android.os.Message.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.send(_arg0);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

可以直接把parcel就轉化成了Message,因為Message本身就已經實現了Parcel。
1. AIDL支持哪些數據類型的傳遞?
     -  基本數據類型
     -  String和CharSequence
     -  List只支持ArrayList,且里面的每一個數據類型都必須要支持AIDL
     -  Map只支持HashMap, 且每一個元素都要支持AIDL
     -  所有實現了Parcelable接口的對象
     -  所有AIDL接口本身也可以在AIDL中使用,比如那些什么什么IxxxxManager這些interface
1. 由于ArrayList本身是線程不安全的,能用什么來替使用在binder中?
    使用CopyOnWriteArrayList這個線程安全的去代替,因為它支持并發讀寫,雖然它并不是繼承的ArrayList而是繼承的List,但是依然可以使用,同理ConcurrentHashMap也可以作為并發讀寫的Map來頂替HashMap進行使用。
1. aidl文件中的方法什么時候使用in,inout,out?
http://www.lxweimin.com/p/ddbb40c7a251
    這邊總結一下,in就是指這個參數只能由客戶端傳到服務端,服務端可以從方法中拿到這個參數,但是隨便服務端怎么該都不會影響客戶端的結果;out代表這個參數只能由服務端傳到客戶端,客戶端可以從方法中拿到這個參數,但是隨便怎么改動,服務端是不會變得;inout表示,這個參數可以從客戶端傳給服務端,也可以從服務端寫到客戶端,而且只要一次transact后,兩邊的參數就會變得一樣。直接講是很難懂的,直接從onTransact方法就可以一目了然。(已拷貝到everNote)
1. 如何解決AIDL傳輸中,遠程和本地不是一個對象的問題?
    我們在使用binder機制的時候,會發現,本地傳給服務端的對象和服務端獲取到的對象不是同一個對象的問題,這是由于在中間的傳輸過程當中都使用到了Parcelable的反序列化,也就是說實際上服務端獲得的真正生成的是將對象parcelable反序列化生成的一個新的對象。我們在如下情況下會對這種情況體會格外的深刻。
    我們需要在遠程服務端注冊多個監聽器,我們可以從本地上傳監聽器到遠程服務端,但是當我們需要對監聽器解注冊的時候發現無法進行解注冊,因為遠程獲得的我們需要解注冊的監聽器和它監聽器容器中沒有匹配的。
   這個時候我們需要使用RemoteCallbackList接口。
  之前如果需要我們在遠端服務去維護一個listener的列表,我們會使用CopyOnWriteArrayList去實現,這個類使用起來和list的使用是一樣的,但是當我們替換成RemoteCallbackList之后我們就不能用list的方法去實現了,這個是專門的用于遠程服務的監聽器容器以下為使用要點:
   添加監聽直接是:

list.register(listener);

     刪除監聽是

list.unregister(listener);

遍歷回調是:

int N = list.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = list.getBroadcastItem(i);
try {
listener.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
list.finishBroadcast();

內部的實現邏輯實際上就是借助了,雖然對象不一樣,但是兩個listener的asBinder所返回的對象是一個對象:

public boolean unregister(E callback) {
synchronized (mCallbacks) {
Callback cb = mCallbacks.remove(callback.asBinder());
if (cb != null) {
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
return true;
}
return false;
}
}

1. uses-permission和permission的區別
    一個是申明使用到的權限
   一個是自定義權限
1. 關于AIDL的自定義權限
     我們在服務所在的android項目中的清單文件中申明自定義的permission

<permission
android:name="com.ding.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
android:description :對權限的描述,一般是兩句話,第一句話描述這個權限所針對的操作,第二句話告訴用戶授予app這個權限會帶來的后果
android:label: 對權限的一個簡短描述
android:name :權限的唯一標識,一般都是使用 報名加權限名
android:permissionGroup: 權限所屬權限組的名稱
android:protectionLevel: 權限的等級,
normal 是最低的等級,聲明次權限的app,系統會默認授予次權限,不會提示用戶
dangerous 權限對應的操作有安全風險,系統在安裝聲明此類權限的app時會提示用戶
signature 權限表明的操作只針對使用同一個證書簽名的app開放
signatureOrSystem 與signature類似,只是增加了rom中自帶的app的聲明

然后在我們需要調用這個服務的app的清單文件中做權限的請求

<uses-permission android:name="com.ding.permission.ACCESS_BOOK_SERVICE"/>


1. AIDL服務的權限驗證?
    如果是我們自己寫的服務并不是所有人都可以訪問的,那么我們就需要對訪問者進行權限驗證,首先是在onBind方法中可以去判斷有沒有在manifest中加相應的權限:
    ```
@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.ding.permission.ACCESS_BOOK_SERVICE");
        if(check == PermissionChecker.PERMISSION_DENIED){
            return null;
        }
        return binder;
    }

這個checkCallingOrSelfPermission方法的內部實現如下:

@Override
    public int checkCallingOrSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        return checkPermission(permission, Binder.getCallingPid(),
                Binder.getCallingUid());
    }
@Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        try {
            return ActivityManagerNative.getDefault().checkPermission(
                    permission, pid, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

很明顯了,就是通過AMS的checkpermission方法,傳入permission字符串,pid,uid
不單單是onBind,還可以在onTransact里通過判斷是否符合權限,如果如何就返回IBinder對象,如果不符合那么就返回false,因為如果請求成功,會返回true。
我們不單單可以通過權限去限制訪問,也可以通過包名去限制:

String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if(packages != null && packages.length > 0){
    packageName = packages[0];
}
if(!packageName.startsWith("org.ding")){
    return false;
}
  1. contentProvider的跨進程實現
    一般在需要跨進程訪問數據庫的時候需要使用到contentProvider,比如我們訪問通訊錄,日程表等等。
    內部的實現依然是使用的是binder機制
    我們可以通過往UriMatcher中addURI去增加匹配規則,來匹配訪問者所需要訪問的具體表
public static final int USER_CODE = 0;

    public static final int BOOK_CODE = 1;

    static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    private static final String AUTHORITY = "org.ding.provider";

    static {
        uriMatcher.addURI(AUTHORITY, "book", BOOK_CODE);
        uriMatcher.addURI(AUTHORITY, "user", USER_CODE);
    }

我們通過在onCreate方法中返回false還是true來判斷provider有沒有正常的加載
然后我們在具體的數據增刪改查方法中根據uri來判定需要去操作哪一張表

int result = uriMatcher.match(uri);
switch (result){
    case BOOK_CODE :
        tableName = "BOOK"
        break;
    case USER_CODE:
        tableName = "USER"
        break;
}
  1. Socket的訪問
    Socket本身也是跨進程的訪問,看如下我們使用的service去模仿服務端和客戶端之間的交互:
@Override
    public void onCreate() {
        super.onCreate();
        new Thread() {
            @Override
            public void run() {
                //創建TCP連接服務
                createTcpServer();
            }
        }.start();
    }
private void createTcpServer() {
        ServerSocket socket = null;
        try {
            //創建Socket連接,端口號是8868
            socket = new ServerSocket(8868);
        } catch (IOException e) {
            e.printStackTrace();
            //如果無法創建socket,則直接退出
            return;
        }
        while (!mIsDestroy) {
            try {
                final Socket client = socket.accept();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            responseClient(client);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
private void responseClient(Socket client) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));
        //通過循環不斷的去獲得對方發給你的消息
        while (!mIsDestroy) {
            //這里會卡住,只要readline沒有一直read出來信息,就會一直在reader.readLine等著
            //我們可以在readLine的方法中看到,實際上里面是一個死循環,會一直等到讀到為止
            String read = reader.readLine();
            if (TextUtils.isEmpty(read)) {
                break;
            }
            //也可以通過out不斷的去輸出信息給對方
        }
    }

只要沒有destory,就不斷的去讀取socket中的信息,然后在activity端,在onCreate方法中

Intent intent = new Intent(this, DemoSocketService.class);
        startService(intent);
        new Thread(){
            @Override
            public void run() {
                connectTcpServer();
            }
        }.start();
private void connectTcpServer() {
        while (mSocket == null) {
            try {
                mSocket = new Socket("localhost", 8868);
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())), true);
            } catch (IOException e) {
                SystemClock.sleep(1000);
                e.printStackTrace();
            }
        }
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
            while (!MainActivity.this.isFinishing()){
                String msg = reader.readLine();
                Log.e("xxxxx", "     msg     :    " +msg);
            }
            reader.close();
            mSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            mPrintWriter.close();
        }

    }

客戶端通過去操作mPrintWriter.println來進行與socket的另一端進行交流
這里有一個點需要注意,由于我們在socket端的reader使用的是reader.readLine()方法,那么必須要在客戶端出現了換行符才會導致socket端可以read出一行。

  1. PrintWriter,BufferedWriter,OutputStreamWriter的關系說明
    

首先需要注意的是,在socket中使用到PrintWriter的時候,構造一定要傳入true,使其自動flush,否則數據是發不出去的
此處要糾正上面的一個問題,作者的有一些構造函數實際上市可以很簡單的寫出來的,就比如

mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())), true);

實際上我們使用:

mPrintWriter = new PrintWriter(mSocket.getOutputStream(), true);

就可以了,因為在PrintWriter(OutputStream out,boolean autoFlush)已經幫我們去實現了

public PrintWriter(OutputStream out, boolean autoFlush) {
        this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);

        // save print stream for error propagation
        if (out instanceof java.io.PrintStream) {
            psOut = (PrintStream) out;
        }
    }

這三者都是輸出流的打印, PrintWriter是可以打印任何東西,BufferedWriter只能打印字符流,而最后的OutputStreamWriter就是輸出流打印,實際上三者是一種嵌套的關系,PrintWriter最終的構造都需要new出后兩者的對象,只是源碼幫我們封裝了。而由于PrintWriter直接可以自動flush,且println可以自動換行,而BufferedWriter由于其局限性,需要newLine方法來進行換行,所以socket中推薦使用PrintWriter。

  1. thread.sleep()和SystemClock.sleep()方法的區別
    兩個的效果是一樣的,但是前者是會被interuptException打斷的
    前者是java的方法,后者是android的方法
  2. countdownlatch的使用
    相當于一個鎖,我們來看相關使用方法的一個例子:

private void testCountDownLatch(){
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
onclick(){
countDownLatch.countDown();
}

我們可以給CountDownLatch來進行賦值,每一次count都-1,當countDownLatch調用await方法的時候只要countDownLatch的值大于0就會堵塞。
1. 談一談transact方法里的最后一個flags參數,和linkDeath方法里的flags參數?
這里我們需要確認的一點是在IBinder類中的所有的flags都是指同一個東西,我們可以從transact方法的標注中看到:
  • @param flags Additional operation flags. Either 0 for a normal
  • RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
我們看到一般0表示一般的RPC,或者使用FLAG_ONEWAY,
那么這個FLAG_ONEWAY到底是什么意思呢?
>  接口中常量FLAG_ONEWAY:客戶端利用binder跟服務端通信是阻塞式的,但如果設置了FLAG_ONEWAY,這成為非阻塞的調用方式,客戶端能立即返回,服務端采用回調方式來通知客戶端完成情況。另外IBinder接口有一個內部接口DeathDecipient(死亡通告)。
    
  所以一般我們使用的情況都是傳0.
1. 當android中的服務特別多的時候,能不能有什么辦法去管理binder呢
我們可以專門的搞一個service去,管理相應的binder,我們這里先去創建幾個AIDL。
IBookManager.aidl

interface IBookManager {
List<Book> getBook();

void addBook(in Book book);

void registerOnNewBookArrived(IOnNewBookArrivedListener listener);

void unregisterOnNewBookArrived(IOnNewBookArrivedListener listener);

}

IUserManager.aidl

interface IUserManager {

void getNewUser(String user);

}

IBinderPool.aidl

interface IBinderPool {

IBinder queryBinder(int binderCode);

}

三個aidl文件,前兩個代表的功能binder,最后一個代表的是工具binder,經過rebuild project之后就得到了三個相應的java文件
作者原文中實際上是比較復雜的,其實為了說明這個binder池的使用,我們可以不用再去額外的價一個BinderPool類,我們直接在Activity中進行遠程Service的連接。
其實對于開發者來說,使用AIDL,只需要實現3個東西就可以了,AIDL生成的IInterface實現類,實現了binder的service類,綁定service的activity類,原文的作者只是為了更穩健的去實現相應的功能。
看一下我們的service類:

public class BinderPoolService extends Service {

IBinderPool binderPool;

private static final int USER_CODE = 0;
private static final int BOOK_CODE = 1;

@Override
public void onCreate() {
    super.onCreate();
    binderPool = new IBinderPool.Stub() {
        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case USER_CODE:
                    binder = new IUserManager.Stub() {
                        @Override
                        public void getNewUser(String user) throws RemoteException {
                            Log.e("xxxxxxxx", "222222222");

                        }
                        ...
                    };
                    break;
                case BOOK_CODE:
                    binder = new IBookManager.Stub() {
                        @Override
                        public void addBook(Book book) throws RemoteException {
                            Log.e("xxxxxxxx", "111111111");
                        }
                        ...
                    };
            }
            return binder;
        }
    };
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return binderPool.asBinder();
}

}

我們在service類實現了IBinderPool接口,并且把實現類的binder對象返回
然后我們在activity中直接通過serviceConnection的方式去獲得binder,然后直接通過queryBinder即可獲得相應的Binder:

new Thread(){
@Override
public void run() {
//直接可以用我們serviceConnection中獲得的IBinder對象替換BinderPool.getInstance(MainActivity.this)
IBookManager bookManager = IBookManager.Stub.asInterface(BinderPool.getInstance(MainActivity.this).queryBinder(1));
try {
bookManager.addBook(new Book("1", "2"));
} catch (RemoteException e) {
e.printStackTrace();
}

            IUserManager userManager = IUserManager.Stub.asInterface(BinderPool.getInstance(MainActivity.this).queryBinder(0));
            try {
                userManager.getNewUser("12");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }.start();
1. 最后綜合所述,進程間通信的方式有如下:
   Bundle(intent的方式),文件共享,aidl,messenger,contentProvider,Socket

----------
#3. View事件體系
1. motionEvent的getX方法和getRawX方法有什么不一樣?
   getY是獲得觸摸點相對與當前view左上角的x坐標,getRawX表示相對于手機屏幕的左上角的x坐標
1. 如何去判斷用戶滑動距離達到多少才算是滑動
   系統有一個專門用來判斷這個的值,TouchSlop,可以通過如下方法獲得:

ViewConfiguration.get(this).getScaledTouchSlop();

/**
* @return Distance in pixels a touch can wander before we think the user is scrolling
*/
public int getScaledTouchSlop() {
return mTouchSlop;
}

備注已經寫的很明顯了,通過查詢源碼我們可以發現它的值是8dp
1. 如何獲取手指滑動的速度?
在OnTouchEvent的方法中,去做速度追蹤我們使用VelocityTracker。
    *  首先我們要啟用VelocityTracker:
       ```
       VelocityTracker velocityTracker = VelocityTracker.obtain();
       velocityTracker.addMovement(event);
       ```
    *  然后在我們需要獲得它的速度的時候通過下面的方法:
       ```
       velocityTracker.computeCurrentVelocity(1000);
        int xVelocity = velocityTracker.getXVelocity();
        int yVelocity = velocityTracker.getYVelocity();
       ```
       告訴系統我們需要測試每1000毫秒的時間內,在X和Y方向移動了多少個像素。
    *  最后當我們不使用的時候,需要把這個VelocityTracker對象釋放掉
      ```
      velocityTracker.clear();
      velocityTracker.recycle();
      ```
     清空,并且回收掉
1. 自定義view里如何設置手勢監聽?
      *  先讓自定義的view實現接口implements GestureDetector.OnGestureListener
      *  然后在構造方法中初始化,手勢監聽器GestureDetector:

detector = new GestureDetector(context, this);
//解決長按屏幕無法拖動的現象
detector.setIsLongpressEnabled(false);

      *  最后在onTouchEvent方法中消費event:
    ```
@Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean consume = detector.onTouchEvent(event);
        return consume;
    }

即可生效,建議是監聽滑動的時候使用onTouchEvent,監聽雙擊或者其他的時候使用GestureDetector

  1. 關于scroller的使用與其方法的內部含義:
    我們一般的scroller使用方式如下:

mScroller = new Scroller(context);
private void smoothScrollTo(int destX, int destY){
int scrollX = getScrollX();
int deltaX = destX - scrollX;
mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);
invalidate();
}

@Override
public void computeScroll() {
    if(mScroller.computeScrollOffset()){
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}
這種smoothScrollTo的方法相信我們平時使用的比較多,這里我們只從X軸去分析了
首先我們調用了getScrollX方法,這個方法返回的是當前展示的view的左邊緣像素值,這里有個問題也許有人會問,為什么不直接使用getX或者getRawX呢,因為我們的scroll移動僅僅移動的是view的內容,而view本身的位置是不發生改變的。所以我們需要專門去調用getScrollX去獲得展示View的左邊緣。
然后我們看方法startScroll,看起來好像是開始滑動了,實則不然:

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}

僅僅只是做了一個賦值,所以如果單單只是調用了這個方法,view是不會移動的,invalidate會要求view重繪,而在view的draw方法中會去調用computeScroll方法,這個方法在View中是空實現,需要子View去復寫,我們可以看一下computeScrollOffset這個方法到底干了什么:

public boolean computeScrollOffset() {
//如果已經滑動完畢那么就退出
if (mFinished) {
return false;
}
//獲取已經過去的時間
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
//如果已經經過的時間還沒有達到規定的滑動時間那么就進入循環
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
//如果是滑動模式,我們根據已經過去的時間然后除以總共需要的時間獲得的百分比來計算這么長時間內我們需要滑動多少距離
//通過不斷的疊加pass的時間的距離值,來產生平緩滑動的效果
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
}

            mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
            
            mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
            // Pin to mMinX <= mCurrX <= mMaxX
            mCurrX = Math.min(mCurrX, mMaxX);
            mCurrX = Math.max(mCurrX, mMinX);
            
            mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
            // Pin to mMinY <= mCurrY <= mMaxY
            mCurrY = Math.min(mCurrY, mMaxY);
            mCurrY = Math.max(mCurrY, mMinY);

            if (mCurrX == mFinalX && mCurrY == mFinalY) {
                mFinished = true;
            }

            break;
        }
    }
    //如果已經到時間了,那么就直接把當前的位置定在最終位置
    else {
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}
1. 那我們如何用動畫的方式去實現上述滑動的等效的效果呢?
    直接通過動畫的onAnimationUpdate方法去回調進行scrollTo:
    ```
private void startScrollViaAnim(final int startX, final int startY, final int destX, final int destY) {
        //創建一個屬性動畫類
        ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(2000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = animation.getAnimatedFraction();
                scrollTo(startX + (int) (fraction * (destX - startX)), startY * (int) (fraction * (destY - startY)));
            }
        });
        animator.start();
    }

我們可以直接new一個ValueAnimator出來,但是一般使用的比較多的構造方法是ofInt和ofFloat,再setDuration就表示在多長一段時間內從幾到幾。
我們的更新監聽每一幀會觸發一次,getAnimatedFraction表示的是當前已經過去了總時間的多少,是一個大于0小于1的值,我們就利用這個值來計算所需要滑動的距離。

  1. 那么除了scroller和動畫之后還有什么可以實現這種平滑的滑動呢?
    可以借助handler來實現,通過循環的postdelay來實現刷新,在handleMessage中去做scrollTo的操作
  2. 請通過最最簡短的偽代碼來描述的view觸摸事件的分發機制?

public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)) {
//如果被攔截了就直接去觸發本view的onTouchEvent方法
consume = onTouchEvent(event);
} else {
//如果沒有被攔截那么就下發到子view去
consume = child.dispatchTouchEvent(event);
}
return consume;
}

1. 事件點擊的傳遞順序?
  最先由硬件獲得傳遞給Activity,然后由activity傳遞給window,然后由root View一層一層的往下傳遞
1. 滑動沖突的處理
    *   父容器攔截
在父容器的onInterceptTouchEvent方法中進行判斷攔截,判斷event.getAction,在ACTION_DOWN和ACTION_UP這兩個type情況下必須要返回false,因為一旦down返回為true,就會導致接下去所有的時間都會被父容器攔截掉,而up也不需要攔截,因為up事件本身沒有太多的意義。
此處有個注意點就是,為了優化滑動的體驗,如果存在沖突的一方是scrollview的話,那么在scrollview滑動的沒有停止之前,要攔截其他的滑動:

if(!mScroller.isFinished()){
mScroller.abortAnimation();
intercepted = true;
}

    *   子容器攔截
 在子容器中攔截的策略是復寫子容器的onInterceptTouchEvent方法,不同的是,需要結合parent.requestDisallowInterceptTouchEvent方法。
大體的思路是在父容器中的攔截方法中除了DOWN方法之外統統攔截;子容器中根據情況在需要子容器攔截的時候執行parent.requestDisallowInterceptTouchEvent方法
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,242評論 25 708
  • 上篇文章介紹了IPC機制的基本概念以及簡單使用,文章鏈接:Android 關于IPC機制的理解(一) 這篇文章主要...
    老實任閱讀 747評論 0 2
  • Jianwei's blog 首頁 分類 關于 歸檔 標簽 巧用Android多進程,微信,微博等主流App都在用...
    justCode_閱讀 5,962評論 1 23
  • 看到別人的不快,講述著,憂傷著,煩惱著,祝好吧,或許大概如此,不曉得應說些什么。 有時倒是會想起自己,有時覺得自己...
    聽雷雷說閱讀 241評論 1 0
  • Part two 今天的課程從一開始就是嚴肅的,總有某個點能觸動到你的內心深處,你不曾解決掉...
    韻妹兒閱讀 295評論 1 2