Blog

hirayclay's blog


  • Home

  • Archives

ContentProvider 调用过程

Posted on 2020-07-23

涉及的类

android.app.ContextImpl
    public ContentResolver getContentResolver()
android.app.ContextImpl.ApplicationContentResolver
    protected IContentProvider acquireProvider(Context context, String auth)   

一般我们这么使用 contentProvider


 getContentResolver().insert(Uri.parse("http://wwww.baidu.com/q/"),null);

getConentProvider 方法是 Context 的,Context 实现类是 ContextImpl

ContextImpl 中方法返回的是一个 成员变量:


    @Override
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }

这个成员变量是一个 ApplicationContentResolver 对象,赋值是在 ContextImpl 的 构造函数中传参赋值:


 private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
            @NonNull LoadedApk packageInfo, @Nullable String splitName,
            @Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
            @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName){
        ....
        mContentResolver = new ApplicationContentResolver(this, mainThread);
        ...

}


这里 ContextImpl 的初始化和 Activity 的启动相关,可以看 Activity 启动流程相关的内容

这里ContentProvider 的实现已经清晰,继续进入 insert() 方法的调用


IContentProvider provider = acquireProvider(url);

进入 ApplicationContentResolver 的 acquireProvider 方法

    @Override
        @UnsupportedAppUsage
        protected IContentProvider acquireProvider(Context context, String auth) {
            return mMainThread.acquireProvider(context,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), true);
        }

又是调用了 ActivityThread 的同名方法:


 @UnsupportedAppUsage
    public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {

        /**
        这里是 首先查看是否已经有缓存的 cp
        */        
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {
            return provider;
        }


        ContentProviderHolder holder = null;
        try {

            //远程调用
            synchronized (getGetProviderLock(auth, userId)) {
                holder = ActivityManager.getService().getContentProvider(
                        getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
            }
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        if (holder == null) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
            return null;
        }

        // Install provider will increment the reference count for us, and break
        // any ties in the race.
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

发起远程调用 拿到一个 ContentProviderHolder

  holder = ActivityManager.getService().getContentProvider(
                        getApplicationThread(), c.getOpPackageName(), auth, userId, stable);


```java 

Android  api 26 以后已经弃用了 ActivityManagerNative(就是手写了实现了一遍 aidl ) ,可以直接看 IActivityManager.aidl 中的定义


然后进入 ActivityManagerService  

```java 

public class ActivityManagerService extends IActivityManager.Stub  implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback

getContentProvider 方法最后调用了 getContentProviderImpl 方法


    private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, int callingUid, String callingPackage, String callingTag,
            boolean stable, int userId) {
        ContentProviderRecord cpr;
        ContentProviderConnection conn = null;
        ProviderInfo cpi = null;
        boolean providerRunning = false;

        synchronized(this) {
            long startTime = SystemClock.uptimeMillis();

            ProcessRecord r = null;
            if (caller != null) {

                //首先拿到调用者的进程记录
                r = getRecordForAppLocked(caller);
                if (r == null) {
                    throw new SecurityException(
                            "Unable to find app for caller " + caller
                          + " (pid=" + Binder.getCallingPid()
                          + ") when getting content provider " + name);
                }
            }

            boolean checkCrossUser = true;

            checkTime(startTime, "getContentProviderImpl: getProviderByName");

            // First check if this content provider has been published...
            cpr = mProviderMap.getProviderByName(name, userId);



            if (providerRunning) {
                cpi = cpr.info;
                String msg;

                //检查 provider的属性,配置了 multiprocess = true 会直接实例化一个实例运行在调用者的进程,也就是说并不会启动 contentProvider
                //所在的进程。后续直接返回到 

                if (r != null && cpr.canRunHere(r)) {
                    if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
                        throw new SecurityException("Content provider lookup "
                                + cpr.name.flattenToShortString()
                                + " failed: association not allowed with package " + msg);
                    }

                    ContentProviderHolder holder = cpr.newHolder(null);

                    holder.provider = null;
                    return holder;
                }

                // Don't expose providers between normal apps and instant apps
                try {
                    if (AppGlobals.getPackageManager()
                            .resolveContentProvider(name, 0 /*flags*/, userId) == null) {
                        return null;
                    }
                } catch (RemoteException e) {
                }

                if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
                    throw new SecurityException("Content provider lookup "
                            + cpr.name.flattenToShortString()
                            + " failed: association not allowed with package " + msg);
                }
                checkTime(startTime,
                        "getContentProviderImpl: before checkContentProviderPermission");
                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
                        != null) {
                    throw new SecurityException(msg);
                }
                checkTime(startTime,
                        "getContentProviderImpl: after checkContentProviderPermission");

                final long origId = Binder.clearCallingIdentity();

                checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");

                // In this case the provider instance already exists, so we can
                // return it right away.
                conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
                        stable);
                if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
                    if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
                        // If this is a perceptible app accessing the provider,
                        // make sure to count it as being accessed and thus
                        // back up on the LRU list.  This is good because
                        // content providers are often expensive to start.
                        checkTime(startTime, "getContentProviderImpl: before updateLruProcess");
                        mProcessList.updateLruProcessLocked(cpr.proc, false, null);
                        checkTime(startTime, "getContentProviderImpl: after updateLruProcess");
                    }
                }

                checkTime(startTime, "getContentProviderImpl: before updateOomAdj");


            if (!providerRunning) {
                try {
                    checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");
                    cpi = AppGlobals.getPackageManager().
                        resolveContentProvider(name,
                            STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
                    checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");
                } catch (RemoteException ex) {
                }
                if (cpi == null) {
                    return null;
                }

                final boolean firstClass = cpr == null;
                if (firstClass) {
                    final long ident = Binder.clearCallingIdentity();

                    // If permissions need a review before any of the app components can run,
                    // we return no provider and launch a review activity if the calling app
                    // is in the foreground.
                    if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {
                        return null;
                    }

                    try {
                        checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
                        ApplicationInfo ai =
                            AppGlobals.getPackageManager().
                                getApplicationInfo(
                                        cpi.applicationInfo.packageName,
                                        STOCK_PM_FLAGS, userId);
                        checkTime(startTime, "getContentProviderImpl: after getApplicationInfo");
                        if (ai == null) {
                            Slog.w(TAG, "No package info for content provider "
                                    + cpi.name);
                            return null;
                        }
                        ai = getAppInfoForUser(ai, userId);
                        cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
                    } catch (RemoteException ex) {
                        // pm is in same process, this will never happen.
                    } finally {
                        Binder.restoreCallingIdentity(ident);
                    }
                }

                checkTime(startTime, "getContentProviderImpl: now have ContentProviderRecord");

                if (r != null && cpr.canRunHere(r)) {
                    // If this is a multiprocess provider, then just return its
                    // info and allow the caller to instantiate it.  Only do
                    // this if the provider is the same user as the caller's
                    // process, or can run as root (so can be in any process).
                    return cpr.newHolder(null);
                }

                if (DEBUG_PROVIDER) Slog.w(TAG_PROVIDER, "LAUNCHING REMOTE PROVIDER (myuid "
                            + (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): "
                            + cpr.info.name + " callers=" + Debug.getCallers(6));

                // This is single process, and our app is now connecting to it.
                // See if we are already in the process of launching this
                // provider.
                final int N = mLaunchingProviders.size();
                int i;
                for (i = 0; i < N; i++) {
                    if (mLaunchingProviders.get(i) == cpr) {
                        break;
                    }
                }

                // If the provider is not already being launched, then get it
                // started.
                if (i >= N) {
                    final long origId = Binder.clearCallingIdentity();

                    try {
                        // Content provider is now in use, its package can't be stopped.
                        try {
                            checkTime(startTime, "getContentProviderImpl: before set stopped state");
                            AppGlobals.getPackageManager().setPackageStoppedState(
                                    cpr.appInfo.packageName, false, userId);
                            checkTime(startTime, "getContentProviderImpl: after set stopped state");
                        } catch (RemoteException e) {
                        } catch (IllegalArgumentException e) {
                            Slog.w(TAG, "Failed trying to unstop package "
                                    + cpr.appInfo.packageName + ": " + e);
                        }

                        // Use existing process if already started
                        checkTime(startTime, "getContentProviderImpl: looking for process record");
                        ProcessRecord proc = getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid, false);
                        if (proc != null && proc.thread != null && !proc.killed) {
                            if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER,
                                    "Installing in existing process " + proc);
                            if (!proc.pubProviders.containsKey(cpi.name)) {
                                checkTime(startTime, "getContentProviderImpl: scheduling install");
                                proc.pubProviders.put(cpi.name, cpr);
                                try {


                                        //这里让 目标进程初始话 ContentProvider,这里进入 到ActivityThread 的 scheduleInstallProvider 方
                                        //法,provider 初始化以后,回调 publishContentproviders

                                    proc.thread.scheduleInstallProvider(cpi);
                                } catch (RemoteException e) {
                                }
                            }
                        } else {
                            checkTime(startTime, "getContentProviderImpl: before start process");
                            proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0,
                                    new HostingRecord("content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name)), false, false, false);
                            checkTime(startTime, "getContentProviderImpl: after start process");
                            if (proc == null) {
                                Slog.w(TAG, "Unable to launch app "
                                        + cpi.applicationInfo.packageName + "/"
                                        + cpi.applicationInfo.uid + " for provider "
                                        + name + ": process is bad");
                                return null;
                            }
                        }
                        cpr.launchingApp = proc;
                        mLaunchingProviders.add(cpr);
                    } finally {
                        Binder.restoreCallingIdentity(origId);
                    }
                }

                checkTime(startTime, "getContentProviderImpl: updating data structures");

                // Make sure the provider is published (the same provider class
                // may be published under multiple names).
                if (firstClass) {
                    mProviderMap.putProviderByClass(comp, cpr);
                }

                mProviderMap.putProviderByName(name, cpr);
                conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
                        stable);
                if (conn != null) {
                    conn.waiting = true;
                }
            }
            checkTime(startTime, "getContentProviderImpl: done!");

            grantEphemeralAccessLocked(userId, null /*intent*/,
                    UserHandle.getAppId(cpi.applicationInfo.uid),
                    UserHandle.getAppId(Binder.getCallingUid()));
        }


//等待 ContentProvider 发布
        // Wait for the provider to be published...
        final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT;
        boolean timedOut = false;
        synchronized (cpr) {
            while (cpr.provider == null) {
                if (cpr.launchingApp == null) {
                    Slog.w(TAG, "Unable to launch app "
                            + cpi.applicationInfo.packageName + "/"
                            + cpi.applicationInfo.uid + " for provider "
                            + name + ": launching app became null");
                    EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,
                            UserHandle.getUserId(cpi.applicationInfo.uid),
                            cpi.applicationInfo.packageName,
                            cpi.applicationInfo.uid, name);
                    return null;
                }
                try {
                    final long wait = Math.max(0L, timeout - SystemClock.uptimeMillis());
                    if (DEBUG_MU) Slog.v(TAG_MU,
                            "Waiting to start provider " + cpr
                            + " launchingApp=" + cpr.launchingApp + " for " + wait + " ms");
                    if (conn != null) {
                        conn.waiting = true;
                    }
                    cpr.wait(wait);
                    if (cpr.provider == null) {
                        timedOut = true;
                        break;
                    }
                } catch (InterruptedException ex) {
                } finally {
                    if (conn != null) {
                        conn.waiting = false;
                    }
                }
            }
        }
        if (timedOut) {
            // Note we do it afer releasing the lock.
            String callerName = "unknown";
            synchronized (this) {
                final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
                if (record != null) {
                    callerName = record.processName;
                }
            }

            Slog.wtf(TAG, "Timeout waiting for provider "
                    + cpi.applicationInfo.packageName + "/"
                    + cpi.applicationInfo.uid + " for provider "
                    + name
                    + " providerRunning=" + providerRunning
                    + " caller=" + callerName + "/" + Binder.getCallingUid());
            return null;
        }

        return cpr.newHolder(conn);
    }

注意两个方法
canRunHere 这个方法用于检查 provider 的配置属性,一般我们可能会这么配置provider


        <provider
            android:name="com.hiray.androidlottie.TestContentProvider"
            android:authorities="com.hiray.androidlottie"
            android:exported="true"
            android:multiprocess="true"
            android:process=":testprovider" />

配置了 multiprocess 属性值等于 true ,那么就符合 canRunHere 的条件,直接在调用者的进程 初始化 provider ,这里 newHolder 方法的 conn 参数 是null,因为不需要后续通过 conn 来进行加减计数

后续注意 scheduleInstallProvider 方法,这里会回到客户端,看ActivityThread 的 同名方法, 利用 Handler 发送了一个 what = INSTALL_PROVIDER 的消息,来到 handleInstallProvider 方法


   private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            if (DEBUG_PROVIDER) {
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

这里是安装好 ContentProvider 后,调用了服务端的 publishContentProviders 方法,使得服务端的 getContentProviderImpl 方法不再阻塞,也就是我们的 调用端的 insert 方法 不被阻塞 ,即 getContentResolver().insert(Uri.parse(“http://wwww.baidu.com/q/"),null); 这句代码执行

继续看 installContentProviders 方法里面调用的 installProvider 方法


    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            if (DEBUG_PROVIDER || noisy) {
                Slog.d(TAG, "Loading provider " + info.authority + ": "
                        + info.name);
            }
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
                c = mInitialApplication;
            } else {
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                Slog.w(TAG, "Unable to get context for package " +
                      ai.packageName +
                      " while loading content provider " +
                      info.name);
                return null;
            }

            if (info.splitName != null) {
                try {
                    c = c.createContextForSplit(info.splitName);
                } catch (NameNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }

            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                if (packageInfo == null) {
                    // System startup case.
                    packageInfo = getSystemContext().mPackageInfo;
                }
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                // XXX Need to create the correct context for this provider.
                localProvider.attachInfo(c, info);
            } catch (java.lang.Exception e) {
                if (!mInstrumentation.onException(null, e)) {
                    throw new RuntimeException(
                            "Unable to get provider " + info.name
                            + ": " + e.toString(), e);
                }
                return null;
            }
        } else {
            provider = holder.provider;
            if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                    + info.name);
        }

        ContentProviderHolder retHolder;

        synchronized (mProviderMap) {
            if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
                    + " / " + info.name);
            IBinder jBinder = provider.asBinder();
            if (localProvider != null) {
                ComponentName cname = new ComponentName(info.packageName, info.name);
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, "
                                + "using existing local provider");
                    }
                    provider = pr.mProvider;
                } else {
                    holder = new ContentProviderHolder(info);
                    holder.provider = provider;
                    holder.noReleaseNeeded = true;
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                    mLocalProviders.put(jBinder, pr);
                    mLocalProvidersByName.put(cname, pr);
                }
                retHolder = pr.mHolder;
            } else {
                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                if (prc != null) {
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, updating ref count");
                    }

                    if (!noReleaseNeeded) {
                        incProviderRefLocked(prc, stable);
                        try {
                            ActivityManager.getService().removeContentProvider(
                                    holder.connection, stable);
                        } catch (RemoteException e) {
                            //do nothing content provider object is dead any way
                        }
                    }
                } else {
                    ProviderClientRecord client = installProviderAuthoritiesLocked(
                            provider, localProvider, holder);
                    if (noReleaseNeeded) {
                        prc = new ProviderRefCount(holder, client, 1000, 1000);
                    } else {
                        prc = stable
                                ? new ProviderRefCount(holder, client, 1, 0)
                                : new ProviderRefCount(holder, client, 0, 1);
                    }
                    mProviderRefCountMap.put(jBinder, prc);
                }
                retHolder = prc.holder;
            }
        }
        return retHolder;
    }

方法内部反射初始化了 ContentProvider ,调用 attachInfo 方法 ,给 provider 传入 context 上下文,并调用 ContentProvider 的onCreate 方法。

目前为止,我们可以知道,整个调用过程初始化了很多东西,客户端服务端来回调用,才能初始化一个ContentProvider ,所以 provider 的初始化是一件非常耗时的过程。

客户端创建完成后,继续回到 AMS 的 getContentProviderImpl 方法的后半段。此时 客户端的 provider 已经安装完成,调用了服务端的 publishContentProviders . 这时候关注一下 getContentProviderImpl 最后等待 provider 安装完成阻塞的代码

 synchronized (cpr) {
            while (cpr.provider == null) {
                if (cpr.launchingApp == null) {
                    Slog.w(TAG, "Unable to launch app "
                            + cpi.applicationInfo.packageName + "/"
                            + cpi.applicationInfo.uid + " for provider "
                            + name + ": launching app became null");
                    EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,
                            UserHandle.getUserId(cpi.applicationInfo.uid),
                            cpi.applicationInfo.packageName,
                            cpi.applicationInfo.uid, name);
                    return null;
                }
                try {
                    if (DEBUG_MU) Slog.v(TAG_MU,
                            "Waiting to start provider " + cpr
                            + " launchingApp=" + cpr.launchingApp);
                    if (conn != null) {
                        conn.waiting = true;
                    }
                    cpr.wait();
                } catch (InterruptedException ex) {
                } finally {
                    if (conn != null) {
                        conn.waiting = false;
                    }
                }
            }
        }

publishContentProviders 方法 部分代码



//取出 cpr ,并且给 provider 赋值,并且唤醒线程。并且做了缓存

 ContentProviderRecord dst = r.pubProviders.get(src.info.name);
                if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);
                if (dst != null) {
                    ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                    mProviderMap.putProviderByClass(comp, dst);
                    String names[] = dst.info.authority.split(";");
                    for (int j = 0; j < names.length; j++) {

                        //缓存起来,后续 如果在此请求 此provider ,可以直接返回
                        mProviderMap.putProviderByName(names[j], dst);
                    }

                    int launchingCount = mLaunchingProviders.size();
                    int j;
                    boolean wasInLaunchingProviders = false;
                    for (j = 0; j < launchingCount; j++) {
                        if (mLaunchingProviders.get(j) == dst) {
                            mLaunchingProviders.remove(j);
                            wasInLaunchingProviders = true;
                            j--;
                            launchingCount--;
                        }
                    }
                    if (wasInLaunchingProviders) {
                        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
                    }
                    synchronized (dst) {
                        dst.provider = src.provider;
                        dst.proc = r;
                        dst.notifyAll();
                    }

由此整个 ContentProvider 的初始化流程结束。

ViewDragHelper在遇到到clickable=true时 无法拖动View的问题解决过程

Posted on 2018-08-28

ViewDragHelper 使用很简单,看到一篇文章说如果支持ViewDragHelper的ViewGroup的子View设置了clickable = true会导致无法拖动子View。但是解决办法是从ViewDragHelper源码出发覆写了,
当时觉得虽然也能解决问题,但是我觉得可以从事件分发的角度来解决。

于是我自定义了一个非常简单的Layout来验证这件事:

public class NSLayout extends LinearLayout {

    private static final String TAG = "NSLayout";

    class DragCallBack extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            return true;
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            return Math.min(Math.max(0, left), getWidth() - child.getWidth());
        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            return Math.min(Math.max(0, top), getHeight() - child.getHeight());
        }

    }

    private ViewDragHelper dragHelper;


    public NSLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        dragHelper = ViewDragHelper.create(this, new DragCallBack());
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return dragHelper.shouldInterceptTouchEvent(ev);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        dragHelper.processTouchEvent(event);
        return true;
    }
}

布局文件

     <com.viewdraghelper.NSLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <View
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:clickable="true"
            android:background="@color/colorPrimary"/>

    </com.viewdraghelper.NSLayout>

结果发现设置clickable= true确实是没法拖动这个View。原因是因为clickable = true会导致这个View在ACTION_DOWN事件发生的时候就把事件接手了,当然了,由于后续事件还是要先经过NSLayout的onInterceptTouchEvent也就是说还会走shouldInterceptTouchEvent。而前面说过可以通过覆写
ViewDragHelper.CallBack的getViewHorizontalDragRange和getViewVerticalDragRange任意一个方法返回大于0的数值就可以解决这个问题。这样能解决的道理很简单,当返回了大于0的数值后,最后在MOVE事件来临的时候会触发后续的逻辑将mDragState置为STATE_DRAGGING,导致最后shouldInterceptTouchEvent返回true,直接拦截了后续事件,View只能接收到一个Cancel事件结束。

那么从事件分发的角度来说,也可以解决,其实这个问题和RecyclerView的Item点击情况有点像,当DOWN事件发生,如果没有Move,直接UP就会点击Item,如果发生了Move就会滚动RecyclerView这中情形下的item 点击和这里的View设置了clickable = true接收了事件是一样的,RecyclerView的滚动和View被拖动是一样,可以一一对应起来。只不过RecyclerView可以处理好是自己内部处理得好。这里我们可以这样处理,当DOWN事件发生的时候,我们copy这个DOWN事件保存起来,如果后续有Move事件,那么说明是要拖动View,把之前保存的DOWN手动分发给ViewDragpHelper,走拖动逻辑。

修改之后的NSLayout代码只改变了一点:

    private MotionEvent downEv;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            downEv = MotionEvent.obtain(ev);
        } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            if (downEv != null) {
                onTouchEvent(downEv);
                downEv = null;
            }
        }
        return dragHelper.shouldInterceptTouchEvent(ev);

    }

思路很简单,就是保存DOWN事件,后续发生Move,就认为是要拖动View,把之前保存的DOWN事件重放一遍,让ViewDragHelper认为是一次完整的事件流。
这样处理同样也可以解决问题。UltraPtr这个库里面就有很多地方像这样手动分发事件。

ProducerArbiter

Posted on 2018-08-16

Producer并不是一层不变

前面说过Subscriber可以通过setProducer设置Producer,而且这个方法也是支持并发调用的,意味着我们的Producer是可能不断变化的————换个说法————我们的数据源可能会改变。关于Producer的逻辑看这个RxJava中的 Producer。但是那篇文章没有提到的是,如果我们的Producer在中途改变了,会发生什么情况。举个例子(这个例子实际来自RxJava开发者的一篇进阶博文)。这个系列的文章真的非常不错,能够看到很多我们不会注意到的问题,也可以窥见早期RxJava实现上的一些影子,对于进一步理解Rx帮助很大。

给定两个Observable,希望观察第一个Observable,当一个Observable结束之后,观察第二个Observable,第二个Observable结束了,那么才会结束,自定义了一个TheObserve操作符:

    public static final class ThenObserve<T> implements Observable.Operator<T, T> {
        final Observable<? extends T> other;
        public ThenObserve(Observable<? extends T> other) {
            this.other = other;
        }

        @Override
        public Subscriber<? super T> call(Subscriber<? super T> child) {
            Subscriber<T> parent = new Subscriber<T>(child, false) {
                @Override
                public void onNext(T t) {
                    child.onNext(t);
                }
                @Override
                public void onError(Throwable e) {
                    child.onError(e);
                }
                @Override
                public void onCompleted() {
                    other.unsafeSubscribe(child);
                }
            };
            child.add(parent);
            return parent;
        }
    }

    Observable<Integer> source = Observable
                .range(1, 10)
                .lift(new ThenObserve<>(Observable.range(11, 90)));

    TestSubscriber<Integer> ts = new TestSubscriber<>();
    ts.requestMore(20);

    source.subscribe(ts);

    ts.getOnNextEvents().forEach(System.out::println);

结果输出了1到30一共30个数字,并不像预想的先输出1-10 然后输出剩下的11-20 一共20个数字。
我们知道Subscriber可以向上游请求数据,如果没有设置Producer,内部有个requested计数器会将这个请求先保存起来,待到调用了setProducer的时候会把请求传递到上游。
而问题在于这个计数器只负责累计计数,并不会在请求已经到达的时候,减去已经完成的请求。那么在这个例子导致的问题就是第一个range(1,10)发出了10个数后,紧接着数据源变成了range(11,90),此时这个range(11,90)依然得到这个 数值为20的requested计数器 ,所以依然发射了 20个数字,所以最后导致一共产生了30个数据。那么应对这种数据源发生变化的场景我们需要用到ProducerArbiter。

先修改这个例子,让ProducerArbiter正确的发挥作用,首先,ProducerArbiter的作用是作为中间枢纽,充当上下游的Producer,Producer发生改变对于上下游是透明的。

修改后:

    public static final class ThenObserve<T> implements Observable.Operator<T, T> {
        final Observable<? extends T> other;

        public ThenObserve(Observable<? extends T> other) {
            this.other = other;
        }

        @Override
        public Subscriber<? super T> call(Subscriber<? super T> child) {
            ProducerArbiter arbiter = new ProducerArbiter();

            Subscriber<T> parent = new Subscriber<T>() {
                @Override
                public void onNext(T t) {
                    child.onNext(t);
                    arbiter.produced(1);
                }

                @Override
                public void onError(Throwable e) {
                    arbiter.setProducer(null);
                    child.onError(e);
                }

                @Override
                public void onCompleted() {
                    arbiter.setProducer(null);
                    //产生新的subscriber用来接收producer的改变
                    DelegatingSubscriber<T> subscriber = new DelegatingSubscriber<>(child, arbiter);
                    child.add(subscriber);
                    other.unsafeSubscribe(subscriber);
                }

                @Override
                public void setProducer(Producer p) {
                    arbiter.setProducer(p);
                }
            };
            child.add(parent);
            child.setProducer(arbiter);
            return parent;
        }
    }


    static class DelegatingSubscriber<T> extends Subscriber<T>{
        Subscriber<? super  T> actual;
        ProducerArbiter arbiter;


        public DelegatingSubscriber(Subscriber<? super  T> downstream,ProducerArbiter arbiter){
            this.actual = downstream;
            this.arbiter = arbiter;
        }
        @Override
        public void onCompleted() {
            arbiter.setProducer(null);
            this.actual.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            arbiter.setProducer(null);
            this.actual.onError(e);
        }

        @Override
        public void onNext(T o) {
            this.actual.onNext(o);
            arbiter.produced(1);
        }

        @Override
        public void setProducer(Producer p) {
           arbiter.setProducer(p);
        }
    }

从最后的实现看,child Subscriber是消费者,需要从第一个range Observable和other Observable两处获取数据,由于数据源是不稳定的,所以child实际上只设置了ProducerArbiter一个Producer,实际Producer的切换交给了ProducerArbiter,这样数据源切换对于child 来说是透明的。
这里具体这做了三件事情:给child 设置ProducerArbiter;ProducerArbiter在数据源发生改变时切换到其他Producer;数据产生时调用producer(n)更新计数。

ProducerArbiter 实现原理

如果是我们来实现ProducerArbiter以此满足上面 例子中的需要,其实我们要做的就是每次请求到来的时候增加计数,当有事件被消耗的时候减少计数,同时支持串行访问,从这样的角度我们再去看ProducerArbiter的源码,可能就会比较容易理解。一共有四个方法:

  • java public void request(long n)
  • java public void setProducer(Producer newProducer)
  • java public void produced(long n)
  • java public void emitLoop()

request方法:

public void request(long n) {
        if (n < 0) {
            throw new IllegalArgumentException("n >= 0 required");
        }
        if (n == 0) {
            return;
        }
        synchronized (this) { 
            if (emitting) {
                missedRequested += n; //1
                return;
            }
            emitting = true;
        }
        boolean skipFinal = false; //2
        try {
            long r = requested;
            long u = r + n;
            if (u < 0) {
                u = Long.MAX_VALUE;
            }
            requested = u;

            Producer p = currentProducer;
            if (p != null) {
                p.request(n);//3
            }

            emitLoop();//4
            skipFinal = true;
        } finally {
            if (!skipFinal) {
                synchronized (this) {
                    emitting = false;
                }
            }
        }
    }

1) 用missedRequested保存没有处理的请求,因为并发访问的原因,此时可能有其他线程在调用,所以先保存起来。

2) skipFinal 实际上的作用是决定是否跳过最后的finally代码块(当然实际并不能跳过,finally代码块的作用只是为了预防emitLoop方法发生错误而没有安全的将emitting 置为false,导致一直阻塞后续的请求)。

3) 这里必须详细解释为什么是请求n,而不是requested。因为下游发出数据请求的时候,这些请求积累是发生在每个Producer上的,此刻的n实际是下游在向currentProducer请求,而requested是之前积累的,可能是之前的missedProducer上积累的请求,更加详细的逻辑会在emitLoop中。

4) emitLoop的作用实际就是真正调用实际的Producer 请求数据的过程。代码能够走到这一步说明emitting = false,此时没有来自其他线程的数据请求,执行emitLoop漏循环处理已经积累的请求。

setProducer方法

    public void setProducer(Producer newProducer) {
        synchronized (this) {
            if (emitting) {
                missedProducer = newProducer == null ? NULL_PRODUCER : newProducer;//1
                return;
            }
            emitting = true;
        }
        boolean skipFinal = false;
        try {
            currentProducer = newProducer;
            if (newProducer != null) {
                newProducer.request(requested);//2
            }

            emitLoop();
            skipFinal = true;
        } finally {
            if (!skipFinal) {
                synchronized (this) {
                    emitting = false;
                }
            }
        }
    }

1) 如果传入的newProducer是null则会把missedProducer设置成NULL_PRODUCER,可以说是一个标记,在emitLoop中作为判断条件使用。
2) 请求了requested个,貌似和前面request方法的//3处有点不同,这是因为如果切换了Producer,那么之前的Producer没有生产的数据,再也不会有机会产生,所以这些
积累下的数据就应该全部交给新的Prodcuer来产生。

emitLoop方法

public void emitLoop() {
        for (;;) {
            long localRequested;
            long localProduced;
            Producer localProducer;
            synchronized (this) {
                localRequested = missedRequested;//1
                localProduced = missedProduced;
                localProducer = missedProducer;
                if (localRequested == 0L
                        && localProduced == 0L
                        && localProducer == null) {//2
                    emitting = false;
                    return;
                }
                missedRequested = 0L;//3
                missedProduced = 0L;
                missedProducer = null;
            }

            long r = requested;

            if (r != Long.MAX_VALUE) {//1
                long u = r + localRequested;
                if (u < 0 || u == Long.MAX_VALUE) {
                    r = Long.MAX_VALUE;
                    requested = r;
                } else {
                    long v = u - localProduced;
                    if (v < 0) {
                        throw new IllegalStateException("more produced than requested");
                    }
                    r = v;
                    requested = v;
                }
            }
            if (localProducer != null) {//2
                if (localProducer == NULL_PRODUCER) {
                    currentProducer = null;
                } else {
                    currentProducer = localProducer;
                    localProducer.request(r);
                }
            } else {//3
                Producer p = currentProducer;
                if (p != null && localRequested != 0L) {
                    p.request(localRequested);
                }
            }
        }
    }

emitLoop方法比较长,直接看真的容易看不懂,我也是最开始卡在这里很久,后来发现最好是用单一Producer的角度分析,从简化的角度理解,如果此时一直都只有一个Producer,不存在Producer切换,那么可以认为此时的ProducerArbiter实际是一个普通的Producer,这里的emitLoop方法实际上处理的只是missedRequested,实际上和RangeProducer(以RangeProducer为例)的背压处理情况下的slowPath方法极度相似————不停的处理积累的请求直到把积累的请求完全处理完。

此时再来看emitLoop,其实只是多了Producer切换的情况,其实如果我们把Producer也看作是一种数据————没错!我们把Producer也看成是request一样,好比某处下游正在请求数据一样,只不过这个请求是请求切换Producer。对应请求数据,我们是寻找合适的Producer把请求发出去,对应Producer我们则应该是寻找合适的数据去发射。 此时我们再来看emitLoop的逻辑,退出漏循环的条件是所有的请求都处理完毕(准确说是:积累的请求处理完、该切换的Producer切换完毕以及累计处理完的事件计数完毕),这里很容易理解。

1) 整个if语句块实际是进行已经消费的事件进行减法计数,减去已经消费的事件数

2) 如果localProducer(实际是missedProducer)不为空,表示存在Producer切换,不过还得继续判断(因为切换的时候可能是传的一个为null的Producer),如果localProducer == NULL_PRODUCER实际上就是把当前的Producer设置成null了也就是不设置数据源,不然的话 切换数据源,并且把累计的请求处理完。

至此整个ProducerArbiter分析完毕,但是ProducerArbiter存在的问题是,没有保证事件是串行发射的,举个例子,此时前面的request正在被Producer A执行,正在发射事件给subscriber,但是紧接着切换了Producer B,此时Producer B也开始给Subscriber发射事件,这样导致的结果就是事件发射是并行的,必然会发生问题,这也显然不是我们想要的。其实我们这时候就需要ProducerObserverArbiter,看名字就是知道它还是个观察者。

RxJava Scheduler一点理解

Posted on 2018-08-15

subscribeOn

通过subscribeOn 和 ObserveOn 两个方法rxjava可以灵活的指定任务执行的线程和指定收到事件的线程
直接看源码:

        public final Observable<T> subscribeOn(Scheduler scheduler, boolean requestOn) {
        if (this instanceof ScalarSynchronousObservable) {
            return ((ScalarSynchronousObservable<T>)this).scalarScheduleOn(scheduler);
        }
        return unsafeCreate(new OperatorSubscribeOn<T>(this, scheduler, requestOn));
    }

使用了lift操作,看一下OperatorSubscribeOn这个操作符,重点是call方法:

   @Override
    public void call(final Subscriber<? super T> subscriber) {
        final Worker inner = scheduler.createWorker();

        SubscribeOnSubscriber<T> parent = new SubscribeOnSubscriber<T>(subscriber, requestOn, inner, source);
        subscriber.add(parent);
        subscriber.add(inner);

        inner.schedule(parent);
    }

这个SubscribeOnSubscriber类型的parent实际是个Action0,然后inner.schedule(parent)直接让这个任务在指定的线程执行了,
然后事件到来的时候,简单调用这个call方法参数传来的Subscriber 调用一下onNext onError onComplete就完了。嗯,其实也很简单,一下就懂了,就是把source Observable的订阅放到一个指定的Scheduler中执行,然后事件也会在所在的Scheduler中发出来。其实可以得出一个结论:无论subscribeOn调用多少次,调度都只会在第一次调用subscribeOn指定的线程中执行。比如一个Observable 调用了subscribeOn(Schedulers.io).subscribeOn(Schedulers.compution()),其实这样做的效果可以拆分看,第一次调用subscribeOn把调度指定在io线程,那么后面的调度就是想把 ‘把调度指定在io’ 的调度指定在computation线程,换个说法,我有一个操作是要指定调度在io线程 ,只不过我把这个操作放在了computation线程去执行而已,有种脱了什么放什么的多余+_+(实在找不出什么恰当的比喻了)。

我们以一个小栗子来看一下整个流程:


 val observable = Observable.create(Observable.OnSubscribe { subscriber ->
            Log.i("source===>", Thread.currentThread().name)
            for (i in 0..0) {
                subscriber.onNext(i)
            }

            subscriber.onCompleted()
        })
 val map = observable
                .observeOn(Schedulers.computation())
                .subscribeOn(Schedulers.newThread())
                .map(Func1 { integer ->
                    Log.i("map===>", Thread.currentThread().name)
                    integer!!.toString()
                })


 map.observeOn(Schedulers.newThread())
                .subscribe(Action1 { s -> Log.i("onNext===>", Thread.currentThread().name) })

首先创建一个简单的发射一个数字的Observable,然后调用map操作转换成string 然后打印出来。注意subscribeOn 和ObserveOn的位置,我们是先observeOn 然后subscribeOn
打印结果:

    
    source===>: RxNewThreadScheduler-2
    map===>: RxComputationScheduler-1
    onNext===>: RxNewThreadScheduler-1
    

可以看到最后的onNext 调用并没有像预想的那样 发生在observeOn指定的computation 线程中,而是subscribeOn指定的创建的新线程中。
其实结合前面的subscribeOn的源码分析可以知道,调用subscribeOn之后的所有操作其实都会在subscribeOn 指定的线程中,这也是为什么map 和subscribe 两个操作都发生在RxNewThreadScheduler的原因。

observeOn

ObserveOn和SubscribeOn不太一样,subscribeOn方法是放在哪儿都可以调用多次也只有第一次调用的效果。ObserveOn也可以多次调用,但是每次都会生效,要理解清楚还得看代码,直接进入OperatorObserveOn操作符的call方法


       @Override
    public Subscriber call(Subscriber child) {
        if (scheduler instanceof ImmediateScheduler) {
            // avoid overhead, execute directly
            return child;
        } else if (scheduler instanceof TrampolineScheduler) {
            // avoid overhead, execute directly
            return child;
        } else {
            ObserveOnSubscriber parent = new ObserveOnSubscriber(scheduler, child, delayError, bufferSize);
            parent.init();
            return parent;
        }
    }

不同的是 observeOn 的call方法是有返回值的,对于很多call方法有返回值的操作符,其实都可以认作是代理模式。包装了下游的subscriber,生成新的subscriber,然后让这个新的subscriber订阅上游observable,自己内部先处理,然后转发给下游的subscriber,达到代理的目的。我们再看一下这个ObserveOnSubscriber: 1)

         @Override
        public void onNext(final T t) {
            if (isUnsubscribed() || finished) {
                return;
            }
            if (!queue.offer(NotificationLite.next(t))) {
                onError(new MissingBackpressureException());
                return;
            }
            schedule();
        }

当接收到上游发来的事件时,调用onNext,然后先存入队列,存入成功,则会执行schedule方法进行调度,schedule方法了解一下:

2)


protected void schedule() {
if (counter.getAndIncrement() == 0) {
recursiveScheduler.schedule(this);
}
}

非常简单的一句,如果当前没有任务(发射事件)调度,那么开始,并且把计数器加一,这里实际是对多线程的考虑,同一时刻只能有一个线程进行调度 。结合上面的onNext方法一起看就是——————如果有任务,先放入队列,放不进去就调用onError,不然就调度,而调度的话又必须满足当前没有其他线程在调度。调度任务会执行ObserveOnSubscriber的call方法,这样就实现了线程切换。call方法内部就是让传进来的child subscriber接收上游的事件,到这里我们可以得出结论ObserveOn 其实只对ObserveOn调用之后的操作生效。举个例子(kotlin编写):


val ob = Observable.create(Observable.OnSubscribe { t ->
t.onNext(1)
t.onCompleted()
})
ob.observeOn(Schedulers.io())
.map { it->it.toString() }
.observeOn(Schedulers.computation())
.map { it-> it.toCharArray() }
.subscribe { }

上游简单发射一个Int数字,第一次调用ObserveOn 那么对于这第一个ObserveOn的操作符而言call方法传入的subscriber是下游map 生成的MapSubscriber,所以第一个map的操作发生在io线程,当同理第二个map 也会发生在computation线程。其实到这里可以总结出来ObserveOn方法的作用其实就是将之后的操作调度ObserveOn指定的线程中执行。

3)
call 方法实现:


@Override
public void call() {
long missed = 1L;//能进入到call这个方法,说明进入前,说明只有一次调度(就是本次调度)
long currentEmission = emitted;
final Queue q = this.queue;
final Subscriber<? super T> localChild = this.child;
for (;;) {
long requestAmount = requested.get();//获取下游的请求数量

            while (requestAmount != currentEmission) { //直到把下游的请求都发射完为止
                boolean done = finished;
                Object v = q.poll();
                boolean empty = v == null;

                if (checkTerminated(done, empty, localChild, q)) { //是否已经结束
                    return;
                }

                if (empty) {
                    break;
                }

                localChild.onNext(NotificationLite.<T>getValue(v));

                currentEmission++;
                if (currentEmission == limit) {
                    requestAmount = BackpressureUtils.produced(requested, currentEmission);
                    request(currentEmission);
                    currentEmission = 0L;
                }
            }

            if (requestAmount == currentEmission) {
                if (checkTerminated(finished, q.isEmpty(), localChild, q)) {
                    return;
                }
            }

            emitted = currentEmission;
            missed = counter.addAndGet(-missed);//如果不是0,其他线程可能也在请求,导致新的多个调度任务,那么还得继续处理,记录调度任务数量,进入下次循环,直到任务全部处理完为止
            if (missed == 0L) {
                break;
            }
        }
    }


大致逻辑梳理一下:

  • 首先声明了一个missed = 1L记录需要调度的数量以及一个currentMission记录已经发射的事件数量;
  • 一个for循环嵌套了一个while循环:
    1. while循环的作用就是发射事件,发射事件之前检查是否已经结束,结束的原因可能是已经结束了或者发生错误
    2. 每发射一个事件就计数

RxJava中的 Producer

Posted on 2018-08-15

本文基于 rxjava 1.3.8

背压

Producer本身是作为沟通上下游的一个接口,只有一个方法:

void request(long n);

如果传入的n是Long.MAX_VALUE,表明是放弃背压,上游会有多少就生产多少。比如range()操作符内部的RangeProducer,如果遇到n = Long.MAX_VALUE,直接用fastPath方法,一口气把事件全部发射出去,反之是请求多少生产多少。

但是平时我们调用range操作符,好像都没有考虑过什么背压,都是这样:

            Observable.range(1, 2)
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Integer integer) {
                            System.out.println(integer)
                    }
                });

也同样是一口气把数据全部给我了,似乎也没有调用request(Long.MAX_VALUE)

Range操作符内部

进入到range操作符,实际内部只有这么一句:

    public void call(final Subscriber<? super Integer> childSubscriber) {
        childSubscriber.setProducer(new RangeProducer(childSubscriber, startIndex, endIndex));
    }

RangeProducer这个Producer实际上就是一个fastPath和slowPath,分别对应没有背压和有背压的情况

那么实际childSubscriber是下游的subscriber,也就是我们调用subscriber的时候new 出来的那个,那么问题来了,到这里都没有什么问题,为何range操作符还是会一口气把数据全部发射出去呢?这里setProducer我们漏掉了。
看一下setProducer做了什么。setProducer方法是subscriber的方法,这里很好理解,下游的subscriber需要数据属于消费者,消费者需要通知生产者,所以给消费者一个方法设置合适(不同场景下有不同的生产者)的生产者。

        public void setProducer(Producer p) {
        long toRequest;
        boolean passToSubscriber = false;
        synchronized (this) {
            toRequest = requested;
            producer = p;
            if (subscriber != null) {
                // middle operator ... we pass through unless a request has been made
                if (toRequest == NOT_SET) {
                    // we pass through to the next producer as nothing has been requested
                    passToSubscriber = true;
                }
            }
        }
        // do after releasing lock
        if (passToSubscriber) {
            subscriber.setProducer(producer);
        } else {
            // we execute the request with whatever has been requested (or Long.MAX_VALUE)
            if (toRequest == NOT_SET) {
                producer.request(Long.MAX_VALUE);
            } else {
                producer.request(toRequest);
            }
        }
    }

官方的注释其实已经是对这段代码的最简洁的解释了:如果Subscriber是通过Subscriber(Subscriber)或者Subscriber(Subscriber, boolean)方法设置了subscriber,那么就会对这个subscriber调用setProducer,反之如果没有设置subscriber并且已经有请求到来,那么就会直接调用producer.request(n),其中n是已经累积的请求。

当然官方解释遗漏一个情况,那就是当没有设置subscriber,而且也没有请求到来的时候,那么就会调用producer.request(Long.MAX_VALUE),也就是让上游有多少就生产多少。

当然为了更容易明白这个方法的作用,总结一下:如果Subscriber自身设置了内部Subcriber那么会把这个producer设置给这个内部Subscriber;不然就请求上游(request(n))开始生产积累的数据,没有累积请求那就一口气全部生产完。有些时候我们要深刻理解前面半句,因为setProducer可能会形成很长的调用链:)。

回到最开始的具体的例子range(1,2).subscribe(subscriber),实际range操作内部那个childSubscriber 是我们new的那个subscriber(准确说是包装了之后的SafeSubscriber) 调用setProducer设置了RangeProducer。
由于我们new 的Subscriber是没有设置内置的subscriber的,那么最后实际会走到 “producer.request(Long.MAX_VALUE);”那句来,导致的结果就是RangeProducer 调用fastPath不考虑背压一口气全部生产完数据。

多个操作符的情况

这里我们有一个自定义的操作符,目的是过滤掉奇数,只要偶数:

EvenFilter implements Observable.Operator<Integer, Integer> {
        public Subscriber<? super Integer> call(final Subscriber<? super Integer> child) {

            return new Subscriber<Integer>(child) {

                public void onNext(Integer t) {
                    if ((t & 1) == 0) {
                        child.onNext(t);
                    }
                }


                public void onError(Throwable e) {
                    child.onError(e);
                }


                public void onCompleted() {
                    child.onCompleted();
                }
            };
        }
    }

操作符本身很简单,在onNext那里简单过滤了一下,看上去很完美。我们再写个例子:

     Observable.range(1, 2)
                .lift(new EvenFilter())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(integer);
                    }
                });

逻辑也很简单,最后打印出了 2,符合预期。我们再加上一个操作符take()

     Observable.range(1, 2)
                .lift(new EvenFilter())
                .take(1).subscribe(new Action1<Integer>() {
                    @Override
                    public void call(Integer integer) {
                        System.out.println(integer);
                    }
                })

然后就出问题了,什么都没有打印。有点反常,因为加上了take(1),逻辑上是没有改变的,应该会输出一样的结果。 一直到lift(new EvenFilter())操作都是可以正常往下游发射数据的,那么为什么只是加入了take会变得奇怪导致没有数据打印。很明显问题出在take操作符上。

分析,必须给他分析————range操作符 childSubscriber.setProducer那句实际上childSubscriber是我们下游EventFilter call方法返回的Subscriber,由于EvenFilter 返回的这个Subscriber设置了child这个Subscriber,所以实际上还会调用这个child的setProducer
把RangeProuducer设置给这个child。而这个child实际又是下游take操作符内部返回的Subscriber ,take操作符返回的这个Subscriber的setProducer方法是这样的:

     public void setProducer(final Producer producer) {
                child.setProducer(new Producer() {

                    // keeps track of requests up to maximum of `limit`
                    final AtomicLong requested = new AtomicLong(0);

                    @Override
                    public void request(long n) {
                        if (n > 0 && !completed) {
                            // because requests may happen concurrently use a CAS loop to
                            // ensure we only request as much as needed, no more no less
                            while (true) {
                                long r = requested.get();
                                long c = Math.min(n, limit - r);
                                if (c == 0) {
                                    break;
                                } else if (requested.compareAndSet(r, r + c)) {
                                    producer.request(c);
                                    break;
                                }
                            }
                        }
                    }
                });
            }

这里实际上是调用了下游的child Subscriber(就是我们最后new的Subscriber)setProducer,根据前面的总结,最后会调用这里这个匿名Producer的request方法,并且n = Long.MAX_VALUE,最后会走到producer.request(c)这句,而且c = limit,啊哈!!!!,因为我们是take(1) 所以limit = 1,上游的RangeProducer 生产一个数据之后就没了,没了!导致2不会被发射出来!

做一整个问题就出在take操作符这里,因为take认为自己只要一个数据,所以只向上游请求了一个数据,这其实非常符合take的逻辑,然而它的上游,可以认为此时take的上游是我们的EvenFilter,EvenFilter自身数据来自range,而不是对range发来的数据做缓存,然后根据下游的请求来发射偶数2。所以这里与其说是take的错误,不如说是我们的Operator实现不够完美。那么我们这里其实有三种解决办法,一种是简单的修改EvenFilter

  • request(1)

      public void onNext(Integer t) {
                      if ((t & 1) == 0) {
                          child.onNext(t);
                      }else request(1)
                  }
    

    这样达到的效果是,如果符合要求,发往下游,不然请求下一个数据,这样所有的数据都会被发射出来

  • 使用filter操作符
    或者是使用rxjava提供的操作符,因为我们的EvenFilter实在有点多次一举,只是简单的过滤偶数,可以使用filter操作符:

      Observable.range(1, 2)
                  .filter(new Func1<Integer, Boolean>() {
                      @Override
                      public Boolean call(Integer integer) {
                          return (integer & 1) == 0;
                      }
                  })
                  .take(1)
    
  • 使用其余的构造方法返回Subscriber
    由于我们的EvenFilter返回的Subscriber使用的是Subscriber(subscriber)构造方法,所以使得 会调用child.setProducer,这里我们使用空的构造方法:
 return new Subscriber<Integer>(/*child*/) {

                public void onNext(Integer t) {
                    System.out.println("inner: " + t);
                    if ((t & 1) == 0) {
                        child.onNext(t);
                    }
                }


                public void onError(Throwable e) {
                    child.onError(e);
                }


                public void onCompleted() {
                    child.onCompleted();
                }
            };

为什么这样可以呢,因为这样就不会调用child的setProducer,而是返回的这个Subscriber的setProducer,根据前面总结的,由于这个Subscriber没有内置的Subscriber,最后会导致调用RangeProducer的 request(Long.MAX_VALUE),一口气生产全部的数据

其实这个例子暗含一个教训,我们在实现自己的操作符的时候尽量不要去使用Subscriber(subscriber)这个构造方法返回Subscriber给上游,因为导致的问题是我们操作符自己被跨越了,下游和上游单独在联系,跨越了操作符自己,导致有些问题不能按照上游到下游一条连贯的线去思考,容易出现一些反常识的bug。

Rxjava retryWhen和repeatWhen操作符原理

Posted on 2018-08-08

契机

因为最近使用了mvvm,不再用mvp,并且大量使用RxJava 简化一些场景下的操作。以至发现了一个操作符retryWhen,搜了一些资料,几乎都是 一位叫DanLew的外国人写的一篇文章或者其译文。原文在这 >> ,思路清晰,知道了怎么用,一些关键的注意点,但就是没有分析具体原理和流程是怎样(但是看他提到的一些词,应该是搞明白了内部原理的)。痛定思痛————当然也是觉得这个操作符非常有意思,所以仔细研究一番。(说实话,我也是最近才觉得RxJava有些源码真的值得好好翻一翻)。本文基于rxjava 1.3.8。

retryWhen和repeatWhen真的不一样吗

先看下retryWhen的方法:

      public final Observable<T> retryWhen(final Func1<Observable<Throwable>,Observable> notificationHandler) {
        return OnSubscribeRedo.<T>retry(this, InternalObservableUtils.createRetryDematerializer(notificationHandler));
    }

再看repeatWhen:

     public final Observable<T> repeatWhen(final Func1<Observable< Void>, Observable> notificationHandler) {
        return OnSubscribeRedo.repeat(this, 
        InternalObservableUtils.createRepeatDematerializer(notificationHandler));
    }

几乎可以认为是一样的,除了对传入的handler处理稍微不同以外。另外参数上有点小不一样,retryWhen的参数内的泛型是Observable,
repeatWhen的参数泛型则是Observable。这和两者响应的事件不一样,retry是对错误响应,发生错误了该选择是否重试;repeat则是对完成事件响应,数据发射完之后是否重试,完成事件是没有数据的所以是Void。

不过我看到这个方法最大的两个疑惑是:为什么不是Func1<Throwable,Boolean>类型的参数,根据给的异常返回true false决定是否重试不是很合理吗??然后马上反应过来,返回Observable会更灵活,如果重试的逻辑很复杂,单纯根据一个bool 的true or false来决定是否重试,是满足不了一些场景下的需要的。

retryWhen方法上有一段注释:

    Returns an Observable that emits the same values as the source observable with the exception of an {@code onError}. An {@code onError} notification from the source will result in the emission of a{@link Throwable} item to the Observable provided as an argument to the {@code notificationHandler}
    function. If that Observable calls {@code onComplete} or {@code onError} then {@code retry} will call {@code onCompleted} or {@code onError} on the child subscription. Otherwise, this Observable will resubscribe to the source Observable.

具体含义就是,这个操作符会返回一个Observable(记作o1),o1会发射和源observable一样的数据(源observable可能会抛出异常)。当源Observable 发射错误事件时,会将这个错误传递给一个Observable(记作o2),而这个o2会作为参数传给notificationHandler。因为notificationHandler 返回的也是一个Observable(o3),如果o3 后续发射了complete或者error事件(其实就是调用了onComplete或者onError),那么会导致child subscription 也调用onComplete或者onError,结束整个流程,不然的话(也就是调用了onNext),那么将会重新订阅源Observable——————也就是再次激活源Oservable。

翻译的有点啰嗦。简而言之就是,我用一个代理Subscriber去订阅源Obsevable,从源Observable获取数据,没有发生错误的情况下,就和一个普通正常的Observable的一样,数据发射完了就结束了。不同的是,头可能发生错误,抛出异常,针对这种情况,我们选择怎么处理。给我们的处理方式就是,我给你一个Observable,当源Observable发射错误事件的时候,下游想从源Observable重新尝试订阅(也就是retry的含义)。而repeatWhen则只是稍微不同,repeatWhen响应的是源Observable的complete事件,就是当数据发射完了,是否重新订阅,重复的从源Observable获取数据。

使用


           retryCount = 0
            Observable.create(new Observable.OnSubscribe<Integer>() {
            public void call(Subscriber<? super Integer> subscriber) {
                subscriber.onNext(1 / 0);
                subscriber.onCompleted();
            }
        }).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
            public Observable<?> call(Observable<? extends Throwable> err) {
                return err
                        .flatMap(new Func1<Throwable, Observable<?>>() {
                            public Observable<?> call(Throwable throwable) {
                                System.out.println(throwable);
                                if (throwable instanceof ArithmeticException && retryCount < 3) {
                                    System.out.println("retry ++");
                                    retryCount++;
                                    return Observable.just("");
                                } else {
                                    System.out.println("no retry");
                                    return Observable.empty();
                                }
                            }
                        });

            }
        })

创建了一个必然抛出算术异常的Observable。重试的逻辑是超过三次就放弃重试。这里是直接Observable.just(“”)触发的重试,以及Observable.empty()结束整个流程

这样写是没问题的,但是既然是返回Observable,那么我直接 这样:

    retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {

            public Observable<?> call(Observable<? extends Throwable> err) {

                                if (throwable instanceof ArithmeticException && retryCount < 3) {
                                    System.out.println("retry ++");
                                    retryCount++;
                                    return Observable.just("");
                                } else {
                                    System.out.println("no retry");
                                    return Observable.empty();
                                }
            }

        })

当然是不可以的,这样的话是返回一个和err无关的Observable,Observable.just(“”) 和Observable.empty()把事件发射完毕后,整个流程也就结束了,我们的订阅者也会取消订阅,不再接收消息。所以根本就是没有发挥这个操作符的效果。那么为什么我不使用参数err 这个Observable就会无效?

源码分析

为了搞清楚为什么我们不使用传入的err这个Observable,就会导致retryWhen操作符失效,还是从方法本身入手

retryWhen方法实际返回的是这个

  OnSubscribeRedo.<T>retry(this, InternalObservableUtils.createRetryDematerializer(notificationHandler));

InternalObservableUtils.createRetryDematerializer(notificationHandler) 这句代码的实际返回的是
一个 RetryNotificationDematerializer 作用是把Observable转换成Observable ,然后传给我们自己定义的notificationHandler作为参数 然后返回一个新的Observable

==========go on 2108.8.10(今天又看了下,又有新的发现,之前的分析漏了点东西,不过也更加发觉 1.x版本的一些东西确实写的复杂了,结构不清晰)

继续看内部逻辑(省略一些过于细节的细节[不然语言又是罗哩罗嗦],需要对源码比较熟悉,最好先看一遍):

   final Subject<Notification<?>, Notification<?>> terminals = BehaviorSubject.<Notification<?>>create().toSerialized();
   final Subscriber<Notification<?>> dummySubscriber = Subscribers.empty();
        // subscribe immediately so the last emission will be replayed to the next
        // subscriber (which is the one we care about)
   terminals.subscribe(dummySubscriber);

被 这三句话坑了很久,还让我看了BehaviorSubject的源码很久,发现这几句就是废话,有没有都行。看他的注释,意思是为了后面我们关心的subscriber能够获取到最近的那个事件,先订阅再说。(实际上最近的那个事件根本不存在)

final Action0 subscribeToSource = new Action0() {
            @Override
            public void call() {
                Subscriber<T> terminalDelegatingSubscriber = new Subscriber<T>() {
                    boolean done;

                    @Override
                    public void onCompleted() {
                        if (!done) {
                            done = true;
                            unsubscribe();
                            terminals.onNext(Notification.createOnCompleted());
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        if (!done) {
                            done = true;
                            unsubscribe();
                            terminals.onNext(Notification.createOnError(e));
                        }
                    }

                    @Override
                    public void onNext(T v) {
                        if (!done) {
                            child.onNext(v);
                            decrementConsumerCapacity();
                            arbiter.produced(1);
                        }
                    }
                };
            }
        };

精简了n多代码。这个subscribeToSource Action0的意思就是 新建了一个terminalDelegatingSubscriber订阅源Observable,就是一个代理订阅者,主要的目的是关注源Observable发出的complet 和error事件。为什么是关注complete和error呢,因为这前面说过retryWhen和repeatWhen本质上的逻辑是一样的,只不过retryWhen关注的是
error,repeatWhen关注是complete。complete 和error都被包装成了Notification,然后发射出去。

继续:

final Observable<?> restarts = controlHandlerFunction.call(
                terminals.lift(new Operator<Notification<?>, Notification<?>>() {
                    @Override
                    public Subscriber<? super Notification<?>> call(final Subscriber<? super Notification<?>> filteredTerminals) {
                        return new Subscriber<Notification<?>>(filteredTerminals) {
                            @Override
                            public void onCompleted() {
                                filteredTerminals.onCompleted();
                            }

                            @Override
                            public void onError(Throwable e) {
                                filteredTerminals.onError(e);
                            }

                            @Override
                            public void onNext(Notification<?> t) {
                                if (t.isOnCompleted() && stopOnComplete) {
                                    filteredTerminals.onCompleted();
                                } else if (t.isOnError() && stopOnError) {
                                    filteredTerminals.onError(t.getThrowable());
                                } else {
                                    filteredTerminals.onNext(t);
                                }
                            }

                            @Override
                            public void setProducer(Producer producer) {
                                producer.request(Long.MAX_VALUE);
                            }
                        };
                    }
                }));

这里稍微复杂一点,从里往外看,里面是terminals.lift操作了一下,主要逻辑在返回的新的Subscribeder的onNext这里,也就是对前面发过来的
Notificaiton拦截判断处理一下。一共有三种情况(其实就是决定retry和repeat的地方):

1) t.isOnCompleted() && stopOnComplete 条件如果满足 对应的是retryWhen 结束不会重试
2)t.isOnError() && stopOnError 条件满足 对应的是repeatWhen 结束,不会重复
3)这种情况下我们针对retryWhen看,如果收到的是一个error事件,很明显前面两个条件都不会满足,直接来到这里,调用onNext,传递给下游

我们把这个terminal.lift之后得到的Observable记作 o1。之后这个o1会作为参数传递给我们的controlHandler
。前面说过这个controlHandler不是我们自己定义的那个notificationHandler,而是经过包装之后的RetryNotificationDematerializer。所以controlHandler.call(o1)这句代码展开就是:

    notificationHandler.call(o1.map(ERROR_EXTRACTOR))

进一步展开:

    notificationHanlder.call(o1.map(new Func1<Notification<?>, Throwable> {
        @Override
        public Throwable call(Notification<?> t) {
            return t.getThrowable();
        }
    }))

所以我们定义的notificationHandler里的Observable是这么来的。加上我们自己添加的逻辑之后返回一个名为restarts的Observable

最后,schedule了一个匿名的任务Action0:

     worker.schedule(new Action0() {
            @Override
            public void call() {
                restarts.unsafeSubscribe(new Subscriber<Object>(child) {

                    @Override
                    public void onNext(Object t) {
                        if (!child.isUnsubscribed()) {

                            if (consumerCapacity.get() > 0) {
                                worker.schedule(subscribeToSource);
                            } else {
                                resumeBoundary.compareAndSet(false, true);
                            }
                        }
                    }
                });
            }
        });

注意这个任务让restarts进行订阅(为了方便起见,我们把这个匿名的Subscriber记为s1),然后在收到next事件的时候,执行前面的subscribeToSource任务,也就是向源Observable发起订阅的任务。

那么这里的bug出现了,该怎么触发subscribeToSource这个任务呢??????? 我们可以从后往前推,要触发这个subscribeToSource必须上游
调用onNext吐出事件来,而这里的上游就是restarts,也就是terminals terminals要吐出事件来,必须依赖源Observable吐出事件, 这里就形成了一个互相依赖的困境!!
termials本身既是Observable也是一个Observer,所以terminals.lift 的目的是对自己发出的事件进行拦截 ,但是自己一开始并没有发出事件,然后把这个lift之后的ob传给我们定义的notificationHandler,所以——————最开始的事件还是必须由我们发出来!!

其实是child.setProducer那一行,会导致调用request(Long.MAX_VALUE),导致subscribeToSource被调用了
所以入口就是这句话,然后发生错误,会回调到我们自定义的错误处理逻辑那里,我们前面提到过,为什么不使用传入的err会导致无效,
因为整个链式调用断开了,返回的restarts是我们自己的Observable,那么导致的结果就是,我们用自己的Observable订阅了那个匿名Subscriber,
可能调用一次onNext,然后就结束了。虽然后面确实会调用work.schedule(subscribeToSource)那一行代码,但是由于child已经结束了,订阅关系没了,这个subscribeToSource是不会执行的。但是如果换成是对err进行变换返回的Observable就不一样了,在接收到源Observable 发来的error的时候,会往下传递,一直走到我们的对error处理的逻辑,如果我们的处理是返回了一个Observable.just(“”)之类的,那么下游必然会接收到,也就是在匿名Subscriber那里的onNext调用,导致重新订阅源Observable,不然的话调用onComplete或者onError结束整个流程。

最后我也看了 2.0.0的retryWhen的代码,做了很多修改,整体上来看,结构更加的清晰更容易看明白。所以建议直接看2.0.0的,没有这么绕。

===写的太乱了(Rx确实有些绕,绕明白了,还是很好理解的)==不定期修改此博文

关于事件机制的总结

Posted on 2018-04-19

事件分发影响到自定义控件以及处理一些特殊问题时候特别有用,一直没有总结一下,导致
在碰到问题的时候思路不够清晰,其实对于单个View的事件分发其实很简单,稍微复杂点的其实
是在有ViewGroup的情况下。也不用举什么例子,直接就总结吧:
针对单个View
所有的事件都从ACTION_DOWN开始,入口是dispatchTouchEvent,因为对于单个View没有拦截之说,所以在dispatchTouchEvent里面只是判断View有没有设置touch监听或者点击,如果有,那么事件就接管了,后续的事件都会直接发给这个View

针对ViewGroup:
一共有三个方法 dispatchTouchEvent onInterceptTouchEvent onTouchEvent,主要是说清楚这三个方法之间的关系

dispatchTouch一样是入口,同样是从ACTION_DOWN开始,先是在dispatchTouchEvent里面调用了onInterceptTouchEvent,问ViewGroup自己需要拦截吗,如果拦截了,那么后续事件就交给ViewGroup的onTouchEvent,当然后续事件调用的方法顺序就是:dispatchTouchEvent(ViewGroup)————>onTouchEvent(ViewGroup);但是如果ACTION_DOWN事件被子View接管了,那么后续事件的调用的顺序:
dispatchTouchEvent(ViewGroup)————>onInterceptTouchEvent(ViewGroup)————>onTouchEvent(View)
但是如果View接管了事件,但是后续事件被ViewGroup拦截了的话,那么View还是会收到一个Cancel事件。

其中ACTION_DOWN没有被ViewGroup拦截的情况下子View接受了ACTION_DOWN事件,然而后续事件还会先通过ViewGroup的onInterceptTouchEvent然后发给子View,其实这个机制的设计是非常有意思的,因为有些情况(或者说很多情况)下我们在手指下去的时候还无法判断究竟谁来接管事件,但是到了发生Move这一步,我们基本上就可以知道怎么处理了。比如每天都用到的RecyclerView,我们既可以滑动Item也可以点击,就是靠这个机制才得以实现的,当手指下去然后松手这中间没有Move直接UP,那么就是一个点击事件,发生Move则会判定为滑动。

以上的总结本身在ViewGroup的onInterceptTouchEvent方法中有注释,之前没耐心看,最近因为和事件分发走的近,必须好好看懂这一块

总之View只有一次机会来接管事件,也就是在ACTION_DOWN传过来的时候,不然以后都没机会了,直到下一次ACTION_DOWN来临。假如谁都不管这个事件,那么最后就会一直冒泡到Activity的onTouchEvent中去(这个很好验证,写个空白布局,重写下Activity的onTouchEvent,里面打个日志就知道了 )因为如此,Android也推出了嵌套滚动,其实就是让子View无脑接管事件,但是呢,会在处理事件前先给parent处理,然后剩下的给自己,这样就让有些不好实现的效果(比如联动)好做很多

关于DataBinding的一些知识

Posted on 2018-04-11

Android的DataBinding出来很久了,但是因为我一直用的mvp,出来的时候就简单的 开启了enable =true这句代码弄了下搞了个简单的layout布局就没管了(好吧,貌似只能算单向绑定=_=)。然后最近想深入的看下DataBinding,比如自定义的控件怎么实现双向绑定,一步一步来吧,其实东西真的不多,不过很强大,基本都是注解。

###简单的绑定
先建立一个Worker类:

    public class Worker {

    public int workerId;
    public String name;
    public int wage;
    public String photoUrl;
    public int photoId;

    public Worker() {
    }


    public Worker(int workerId, String name, int wage, int photoId) {
        this.workerId = workerId;
        this.name = name;
        this.wage = wage;
        this.photoId = photoId;
    }
}

这是一个简单的布局文件(data_binding_layout.xml),在TextView上显示Worker的名字:

    <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">


    <data>

        <variable
            name="worker"
            type="com.hiray.mvvm.mvvm.model.Worker" />

    </data>

    <TextView
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:text="@{worker.name}" />  
</layout>

会根据布局文件名字生成一个ViewDataBinding对象————DataBindingLayoutBinding,在代码中设置一下:

       public class DataBindingActivity extends AppCompatActivity {
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                DataBindingLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.data_binding_layout);
                binding.setWorker(new Worker(1205, "Mike", 12000, R.mipmap.boy);
            }
        }

以上是非常简单的一种绑定,只是单向的数据绑定,数据映射到UI上
关于这样的单向绑定有好几个注解

BindingMethods、BindingMethod

比如我想给ImageView加上自定义的属性下载图片

        <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">


    <data>

        <variable
            name="worker"
            type="com.hiray.mvvm.mvvm.model.Worker" />

    </data>

    <ImageView
        app:imageDrawable="@{worker.photoId}"
        android:layout_width="50dp"
        android:layout_height="50dp"/>  
</layout>

这里”app:imageDrawable”这个属性是无法被识别的,会编译出错,我们要通过某种方式告诉系统该怎么做。我们建立一个类 ViewBindingAdapter,使用BindingMethods和BindingMethod注解:

@BindingMethods(
        @BindingMethod(
                type = ImageView.class,
                attribute = "app:imageDrawable",
                method = "setImageResource"
        )
)
public class ImageAttr {


}

其中type是你要绑定的类,attribute是你自己定的,method是ImageView中的方法,也就是告诉ImageView,在xml中碰到app:imageDrawable属性的时候调用ImageView的setImageResource方法。
但是如果我此时不想传入图片id,而是传入一个id的String字符,那怎么办呢,因为ImageView并没有接收String参数来设置图片的方法,那么我们要利用BindConversion转换一下

BindingConversion


public class ImageAttr {

    @BindingConversion
    public static int convertStringToResId(String idString){
        return Integer.parseInt(idString);
    }

}

但是有些时候即使转换了,也没有对应的setter方法可以使用,比如你设置drawableLeft这种属性的时候,是没有setDrawableLeft方法的,只有setCompoundDrawable,那么这时候就可以使用 BindingAdapter这个注解了

BindingAdapter

顾名思义是绑定适配器,如果像设置的属性没有直接的方法,需要转换一下,那么就用到这个注解,比如设置drawableLeft,是没有setDrawableLeft方法的,必须调用view的setCompoundDrawables


public class ImageAttr {

    @BindingAdapter("app:drawableLeft")
    public static void bindDrawableLeft(TextView view, Drawable leftDrawable) {
        view.setCompoundDrawables(leftDrawable, null, null, null);
    }

}

InverseMethod

在双向绑定中,需要对值进行转换
比如我们有个checkbox,如果model中的一个String类型的值是“Alice”就让checkbox选上,反之不勾选,这里就要用到InverseMethod注解
新建一个Converter类,写了两个静态方法:

    public class Converter {

    @InverseMethod("convertStringToBool")
    public static String convertBoolToString(boolean b) {
        if (b)
            return "Alice";
        else return "Unknown";
    }

    public static boolean convertStringToBool(String name) {
        return "Alice".equals(name);
    }
}

布局文件:

    <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="person"
            type="com.hiray.mvvm.mvvm.model.Person"/>
        <import type="com.hiray.mvvm.mvvm.attr.Converter"/>
    </data>

        <android.support.v7.widget.AppCompatCheckBox
            android:checked="@={Converter.convertStringToBool(person.personName)}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

</layout>

给自定义控件支持双向绑定

这里有个自定义的BlinkView,不停的闪烁,由一个bool 类型的值 blink控制是否闪烁,伪代码如下:

    public class BlinkView extends View {

    private Paint paint;
    boolean blink = true;
    ......

    public void setBlink(boolean blink) {
        this.blink = blink;
        invalidate();
    }

    public boolean getBlink(){
        return blink;
    }
    public void toggle() {
        setBlink(!blink);
        if (onBlinkChangeListener != null)
            onBlinkChangeListener.onBlinkChange(blink);
    }

    public interface OnBlinkChangeListener {
        void onBlinkChange(boolean blink);
    }

    private OnBlinkChangeListener onBlinkChangeListener;

    public void setOnBlinkChangeListener(OnBlinkChangeListener onBlinkChangeListener) {
        this.onBlinkChangeListener = onBlinkChangeListener;
    }
}

layout文件中:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="data"
            type="com.hiray.mvvm.mvvm.model.DataHolder" />


    </data>
        <com.hiray.mvvm.mvvm.widget.BlinkView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:blink="@={data.blink}"
            app:blinkColor="@color/colorAccent" />

</layout>

这样写显然不会有任何效果,建立一个名为BlinkViewAdapter的类,使用InverseBindingMethods注解告诉Android在遇到app:blink属性的时候怎么把数据映射到UI上,然后又要告诉AndroidUI怎么映射到数据上,代码如下:

    @InverseBindingMethods(
        @InverseBindingMethod(type = BlinkView.class,
                attribute = "blink")
)
public class BlinkViewAdapter {

//    @InverseBindingAdapter(attribute = "app:blink")
//    public static boolean isBlink(BlinkView view) {
//        return view.getBlink();
//    }

    @BindingAdapter(value = {"app:blinkChanged", "app:blinkAttrChanged"}, requireAll = false)
    public static void setListener(BlinkView view, BlinkView.OnBlinkChangeListener listener,
                                   final InverseBindingListener attrChange) {

        if (attrChange == null && listener != null)
            view.setOnBlinkChangeListener(listener);
        else view.setOnBlinkChangeListener(new BlinkView.OnBlinkChangeListener() {
            @Override
            public void onBlinkChange(boolean blink) {
                attrChange.onChange();
            }
        });

    }

}

其中InverseBindingMethod注解有三个参数:type表示你要绑定的类,attribute就是你要进行绑定的属性(就是写在xml上的属性),method默认按照属性名字去找有没有 isXX 或者getXX方法,不然你就写上method名字,如果没有直接的method名字,可以使用InverseBindingAdapter注解(代码中注释的部分),这是告诉系统在UI发生变化的时候调用什么方法获取UI信息这里Android默认是按照属性名字去找有没有 xxAttrChanged的(如果你自己没有定义的话),当然这个event可以自己定义,比如你定义成”abcdefg&%##$”,那么上面setListener方法的BindingAdapter注解的”app:blinkAttrChanged”也得改成这个。
另外setListener有三个参数,第一个是控件BlinkView自己,第二个是BlinkView.OnBlinkChangeListener 和”app:blinkChanged”对应;第三个是InverseBindingListener是必须的,这个参数的实现在编译期就生成了,就是通知系统UI变化了,要更新ui信息到数据上(更新的方法就是前面的第一部分),可以看下生成的attrChange:

     private android.databinding.InverseBindingListener mboundView3blinkAttrChange = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of data.blink
            //         is data.setBlink((boolean) callbackArg_0)
            boolean callbackArg_0 = mboundView3.getBlink();
            // localize variables for thread safety
            // data.blink
            boolean dataBlink = false;
            // data != null
            boolean dataJavaLangObjectNull = false;
            // data
            com.hiray.mvvm.mvvm.model.DataHolder data = mData;



            dataJavaLangObjectNull = (data) != (null);
            if (dataJavaLangObjectNull) {




                data.setBlink(((boolean) (callbackArg_0)));
            }
        }
    };

看到这里其实就明白了,这里生成的东西都是根据前面的注解来的,收到刷新提示就会调用方法获取UI信息,并且更新数据模型中的值,由此完成了整个的双向的绑定

自行处理Fling导致RecyclerView 滑动点击事件无效

Posted on 2018-04-02

之前写过StackLayoutManager,一个自定义的LayoutManager,最近有同学说滑动之后item 点击无效,发现是滑动之后第一次点击无效,再次点击才能触发点击事件。第一反应觉得很诧异,要么就不触发,怎么还要点击两次才能触发的。带着疑问我调试了一下RecyclerView的onInterceptTouchEvent方法。结果是fling一次后点击item ,onInterceptTouchEvent方法返回了true,也就是事件被拦截了,就是导致Item无法点击的原因,拦截的条件是mScrollState == STATE_DRAGGING。但是明显现在应该处于STATE_IDLE状态,fling之后手指已经离开屏幕了。所以继续追踪,发现RecyclerView的fling事件内部自己有处理,而且fling完之后,会将mScrollState重置为STATE_IDLE,但是因为StackLayoutManager是使用的setOnFlingListener方式,导致没有重置状态,所以之后的第一次点击mScrollState仍然处于STATE_DRAGGING状态,所以被拦截了。但是我们是第二次点击又是可以的,所以肯定是第一次点击的某个地方将mScrollState重置为STATE_IDLE了,找了下,发现RecyclerView的onTouchEvent方法有这么一句

                if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
                    setScrollState(SCROLL_STATE_IDLE);
                }

知道了前因后果之后,我们要做的就是自己处理fling之后应该将mScrollState重置为idle状态,但是RecylerView改变状态的方法并不对外暴露,所以最后我用了反射。

关于AndroidStudio下的idea目录

Posted on 2018-01-24

这个目录一开始看名字只知道和我们工程的工作区间有关,到底是啥我没仔细看过。直到有一天,我不小心把这个文件的内容给删除了(可能是午睡趴在键盘上了),然后会导致工程一直报这个文件的错,于是乎我干脆把这个文件都删了。导致的结果是每次重新打开工程,之前打开过的文件全都不会自动打开,得一个个的点开,瞬间我就有点知道这个文件干嘛的了。其实就是记录我们最近的文件操作,比如你上次退出前打开过的文件,鼠标在哪个位置等等,比如:

我最后鼠标停在红色标记处,也就是整个文件的第1行,第25个文字处

那么在workspace.xml文件中会产生这么一条记录:

应该是下标从0开始的原因,记录的line = 0,column =24
可能没有这条记录,ctrl+s强制保存一次就有了

当然workspace文件里面保存的还有其他信息,比如当然使用的gradle版本等信息

所以最后我自然就去其他工程拷贝了一份直接放到idea目录下就ok了

12

hirayclay

16 posts
29 tags
© 2020 hirayclay
Powered by Hexo
|
Theme — NexT.Muse v5.1.4