Android平台Piwik-SDK源码解析(一)

来源:互联网 发布:网络运营管理岗 面试 编辑:程序博客网 时间:2024/05/22 08:34

本文将对Android平台上的Piwik-SDK进行部分源码解析。

这是Piwik-SDK的github地址:https://github.com/piwik/piwik-sdk-android

我们来看一个追踪页面事件的完整方法:

TrackHelper.track()                .screen("/custom_vars")                .title("Custom Vars")                .variable(1, "first", "var")                .variable(2, "second", "long value")                .with(getTracker());
首先我们调用track()方法获得一个TrackHelper对象:

 public static TrackHelper track() {        return new TrackHelper();    }
该对象拥有screen()、event()等一系列追踪方法,该类还拥有BaseEvent用于上传数据的内部类等,功能繁多。
接下来我们看看screen()方法,它直接new一个Screen对象(Screen同样是TrackHelper的内部类),并在构造方法中初始化入参,super()方法其实是在BaseEvent类中构造一个基础的TrackHelper对象,用于获取一个baseTrackMe对象用作其它用途,在此先不详谈。

 Screen(TrackHelper baseBuilder, String path) {            super(baseBuilder);            mPath = path;        }
同样的,该类的title()方法也初始化了入参:

public Screen title(String title) {            mTitle = title;            return this;        }
然后我们看看它究竟对这些参数做了什么操作:
public TrackMe build() {            if (mPath == null) return null;            final TrackMe trackMe = new TrackMe(getBaseTrackMe())                    .set(QueryParams.URL_PATH, mPath)                    .set(QueryParams.ACTION_NAME, mTitle);            if (mCustomVariables.size() > 0) {                //noinspection deprecation                trackMe.set(QueryParams.SCREEN_SCOPE_CUSTOM_VARIABLES, mCustomVariables.toString());            }            for (Map.Entry<Integer, String> entry : mCustomDimensions.entrySet()) {                CustomDimension.setDimension(trackMe, entry.getKey(), entry.getValue());            }            return trackMe;        }

在Screen类的build()方法里,我们看到他们被保存到了一个TrackMe对象的HashMap中,同样地,若传入了非空的自定义变量对象mCustomVariables,也一并保存。build方法返回了一个TrackMe对象,那这个方法什么时候被调用呢?没错,就是在最后调用的with()方法里:

public void with(@NonNull Tracker tracker) {            TrackMe trackMe = build();            if (trackMe != null) tracker.track(trackMe);        }
每次调用该方法,参数都会被保存在一个TrackMe对象里,然后理所当然就是进行数据的上传了,我们来看它的track()方法:

public Tracker track(TrackMe trackMe) {        boolean newSession;        synchronized (mSessionLock) {            newSession = tryNewSession();            if (newSession) mSessionStartLatch = new CountDownLatch(1);        }        if (newSession) {            injectInitialParams(trackMe);        } else {            try {                // Another thread might be creating a sessions first transmission.                mSessionStartLatch.await(getDispatchTimeout(), TimeUnit.MILLISECONDS);            } catch (InterruptedException e) { Timber.tag(TAG).e(e, null); }        }        injectBaseParams(trackMe);        mLastEvent = trackMe;        if (!mOptOut) {            mDispatcher.submit(trackMe);            Timber.tag(LOGGER_TAG).d("Event added to the queue: %s", trackMe);        } else Timber.tag(LOGGER_TAG).d("Event omitted due to opt out: %s", trackMe);        // we did a first transmission, let the other through.        if (newSession) mSessionStartLatch.countDown();        return this;    }
我们看到它有一个同步方法,判断是否是新的追踪对话然后再进行一些配置操作,在此不详谈。最重要的是这段代码:

if (!mOptOut) {            mDispatcher.submit(trackMe);            Timber.tag(LOGGER_TAG).d("Event added to the queue: %s", trackMe);        } else Timber.tag(LOGGER_TAG).d("Event omitted due to opt out: %s", trackMe);

mOptOut是一个表示禁用的标志,在未被禁用的情况下,mDispatcher调用submit()方法开始上传数据:

public void submit(TrackMe trackMe) {        mEventCache.add(new Event(trackMe.toMap()));        if (mDispatchInterval != -1) launch();    }
我们看到它先把trackMe缓存起来,然后调用launch()方法:

private boolean launch() {        synchronized (mThreadControl) {            if (!mRunning) {                mRunning = true;                Thread thread = new Thread(mLoop);                thread.setPriority(Thread.MIN_PRIORITY);                thread.start();                return true;            }        }        return false;    }
已经很接近了,它使用了一个同步代码块,启动了线程mLoop进行处理,那这个线程做了什么?还是在Dispatcher这个类里:

private Runnable mLoop = new Runnable() {        @Override        public void run() {            while (mRunning) {                try {                    // Either we wait the interval or forceDispatch() granted us one free pass                    mSleepToken.tryAcquire(mDispatchInterval, TimeUnit.MILLISECONDS);                } catch (InterruptedException e) {Timber.tag(LOGGER_TAG).e(e); }                if (mEventCache.updateState(isConnected())) {                    int count = 0;                    List<Event> drainedEvents = new ArrayList<>();                    mEventCache.drainTo(drainedEvents);                    Timber.tag(LOGGER_TAG).d("Drained %s events.", drainedEvents.size());                    for (Packet packet : mPacketFactory.buildPackets(drainedEvents)) {                        boolean success = false;                        try {                            success = dispatch(packet);                        } catch (IOException e) {                            // While rapidly dispatching, it's possible that we are connected, but can't resolve hostnames yet                            // java.net.UnknownHostException: Unable to resolve host "...": No address associated with hostname                            Timber.tag(LOGGER_TAG).d(e);                        }                        if (success) {                            count += packet.getEventCount();                        } else {                            Timber.tag(LOGGER_TAG).d("Unsuccesful assuming OFFLINE, requeuing events.");                            mEventCache.updateState(false);                            mEventCache.requeue(drainedEvents.subList(count, drainedEvents.size()));                            break;                        }                    }                    Timber.tag(LOGGER_TAG).d("Dispatched %d events.", count);                }                synchronized (mThreadControl) {                    // We may be done or this was a forced dispatch                    if (mEventCache.isEmpty() || mDispatchInterval < 0) {                        mRunning = false;                        break;                    }                }            }        }    };
这就是整个追踪的流程:初始化、保存数据、上传数据,其中有许多操作我还没有深究,有兴趣的朋友可以交流讨论。





原创粉丝点击