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?
//www.greatytc.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方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,078评论 25 707
  • 上篇文章介绍了IPC机制的基本概念以及简单使用,文章链接:Android 关于IPC机制的理解(一) 这篇文章主要...
    老实任阅读 695评论 0 2
  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,878评论 1 23
  • 看到别人的不快,讲述着,忧伤着,烦恼着,祝好吧,或许大概如此,不晓得应说些什么。 有时倒是会想起自己,有时觉得自己...
    听雷雷说阅读 235评论 1 0
  • Part two 今天的课程从一开始就是严肃的,总有某个点能触动到你的内心深处,你不曾解决掉...
    韵妹儿阅读 274评论 1 2