//参数代表是否反转动画 privatevoidstart(boolean playBackwards) { if (Looper.myLooper() == null) { thrownewAndroidRuntimeException("Animators may only be run on Looper threads"); } mReversing = playBackwards; mPlayingBackwards = playBackwards; //需要反转动画 if (playBackwards && mSeekFraction != -1) { if (mSeekFraction == 0 && mCurrentIteration == 0) { // special case: reversing from seek-to-0 should act as if not seeked at all mSeekFraction = 0; } elseif (mRepeatCount == INFINITE) { mSeekFraction = 1 - (mSeekFraction % 1); } else { mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction); } mCurrentIteration = (int) mSeekFraction; mSeekFraction = mSeekFraction % 1; } if (mCurrentIteration > 0 && mRepeatMode == REVERSE && (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) { // if we were seeked to some other iteration in a reversing animator, // figure out the correct direction to start playing based on the iteration if (playBackwards) { mPlayingBackwards = (mCurrentIteration % 2) == 0; } else { mPlayingBackwards = (mCurrentIteration % 2) != 0; } } intprevPlayingState= mPlayingState; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; mPaused = false; updateScaledDuration(); // in case the scale factor has changed since creation time //创建AnimatorHandler AnimationHandleranimationHandler= getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running if (prevPlayingState != SEEKED) { setCurrentPlayTime(0); } mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } //开始动画 animationHandler.start(); }
// mPendingAnimations holds any animations that have requested to be started // We're going to clear mPendingAnimations, but starting animation may // cause more to be added to the pending list (for example, if one animation // starting triggers another starting). So we loop until mPendingAnimations // is empty. while (mPendingAnimations.size() > 0) { //如果有等待执行的动画,清除并优先执行这些动画 ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) mPendingAnimations.clone(); mPendingAnimations.clear(); intcount= pendingCopy.size(); for (inti=0; i < count; ++i) { ValueAnimatoranim= pendingCopy.get(i); // If the animation has a startDelay, place it on the delayed list if (anim.mStartDelay == 0) { anim.startAnimation(this); } else { mDelayedAnims.add(anim); } } }
// Next, process animations currently sitting on the delayed queue, adding // them to the active animations if they are ready //将延时队列中的动画加入到准备完成队列,开始执行动画,清空队列 intnumDelayedAnims= mDelayedAnims.size(); for (inti=0; i < numDelayedAnims; ++i) { ValueAnimatoranim= mDelayedAnims.get(i); if (anim.delayedAnimationFrame(frameTime)) { mReadyAnims.add(anim); } } intnumReadyAnims= mReadyAnims.size(); if (numReadyAnims > 0) { for (inti=0; i < numReadyAnims; ++i) { ValueAnimatoranim= mReadyAnims.get(i); anim.startAnimation(this); anim.mRunning = true; mDelayedAnims.remove(anim); } mReadyAnims.clear(); }
// Now process all active animations. The return value from animationFrame() // tells the handler whether it should now be ended intnumAnims= mAnimations.size(); for (inti=0; i < numAnims; ++i) { mTmpAnimations.add(mAnimations.get(i)); } for (inti=0; i < numAnims; ++i) { ValueAnimatoranim= mTmpAnimations.get(i); if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) { mEndingAnims.add(anim); } } mTmpAnimations.clear(); if (mEndingAnims.size() > 0) { for (inti=0; i < mEndingAnims.size(); ++i) { mEndingAnims.get(i).endAnimation(this); } mEndingAnims.clear(); }
// Schedule final commit for the frame. mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);
// If there are still active or delayed animations, schedule a future call to // onAnimate to process the next frame of the animations. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { scheduleAnimation(); } }
voiddoCallbacks(int callbackType, long frameTimeNanos) { CallbackRecord callbacks; synchronized (mLock) { // We use "now" to determine when callbacks become due because it's possible // for earlier processing phases in a frame to post callbacks that should run // in a following phase, such as an input event that causes an animation to start. finallongnow= System.nanoTime(); callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true;
// Update the frame time if necessary when committing the frame. // We only update the frame time if we are more than 2 frames late reaching // the commit phase. This ensures that the frame time which is observed by the // callbacks will always increase from one frame to the next and never repeat. // We never want the next frame's starting frame time to end up being less than // or equal to the previous frame's commit frame time. Keep in mind that the // next frame has most likely already been scheduled by now so we play it // safe by ensuring the commit time is always at least one frame behind. if (callbackType == Choreographer.CALLBACK_COMMIT) { finallongjitterNanos= now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); if (jitterNanos >= 2 * mFrameIntervalNanos) { finallonglastFrameOffset= jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f) + " ms which is more than twice the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Setting frame time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); mDebugPrintNextFrameTimeDelta = true; } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; } } } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]); for (CallbackRecordc= callbacks; c != null; c = c.next) { if (DEBUG_FRAMES) { Log.d(TAG, "RunCallback: type=" + callbackType + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); } c.run(frameTimeNanos); } } finally { synchronized (mLock) { //执行所有的回调 mCallbacksRunning = false; do { finalCallbackRecordnext= callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks != null); } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
//可以直接设置target,propertyName publicstatic ObjectAnimator ofInt(Object target, String propertyName, int... values) { ObjectAnimatoranim=newObjectAnimator(target, propertyName); anim.setIntValues(values); return anim; } publicvoidsetTarget(@Nullable Object target) { finalObjectoldTarget= getTarget(); //老的目标与当前目标不同,取消。设置弱引用目标对象 if (oldTarget != target) { if (isStarted()) { cancel(); } mTarget = target == null ? null : newWeakReference<Object>(target); // New target should cause re-initialization prior to starting mInitialized = false; } } publicvoidstart() { // See if any of the current active/pending animators need to be canceled AnimationHandlerhandler= sAnimationHandler.get(); if (handler != null) { intnumAnims= handler.mAnimations.size(); for (inti= numAnims - 1; i >= 0; i--) { if (handler.mAnimations.get(i) instanceof ObjectAnimator) { ObjectAnimatoranim= (ObjectAnimator) handler.mAnimations.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { //相同的目标和属性,取消之前的动画 anim.cancel(); } } } //等待的动画也一样 numAnims = handler.mPendingAnimations.size(); for (inti= numAnims - 1; i >= 0; i--) { if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) { ObjectAnimatoranim= (ObjectAnimator) handler.mPendingAnimations.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } //延时的动画也一样 numAnims = handler.mDelayedAnims.size(); for (inti= numAnims - 1; i >= 0; i--) { if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) { ObjectAnimatoranim= (ObjectAnimator) handler.mDelayedAnims.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } } if (DBG) { Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration()); for (inti=0; i < mValues.length; ++i) { PropertyValuesHolderpvh= mValues[i]; Log.d(LOG_TAG, " Values[" + i + "]: " + pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " + pvh.mKeyframes.getValue(1)); } } super.start(); } voidanimateValue(float fraction) { finalObjecttarget= getTarget(); if (mTarget != null && target == null) { // We lost the target reference, cancel and clean up. cancel(); return; }
super.animateValue(fraction); intnumValues= mValues.length; for (inti=0; i < numValues; ++i) { mValues[i].setAnimatedValue(target); } } publicvoidsetupStartValues() { //初始化动画 initAnimation();
finalObjecttarget= getTarget(); if (target != null) { finalintnumValues= mValues.length; for (inti=0; i < numValues; ++i) { mValues[i].setupStartValue(target); } } } voidinitAnimation() { if (!mInitialized) { // mValueType may change due to setter/getter setup; do this before calling super.init(), // which uses mValueType to set up the default type evaluator. finalObjecttarget= getTarget(); if (target != null) { finalintnumValues= mValues.length; for (inti=0; i < numValues; ++i) { //初始化getter setter mValues[i].setupSetterAndGetter(target); } } super.initAnimation(); } }
for (Node node : mNodes) { //禁止异步运行node.animation.setAllowRunningAsynchronously(false); }
//设置所有动画的长度 if (mDuration >= 0) { // If the duration was set on this AnimatorSet, pass it along to all child animations for (Node node : mNodes) { // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to // insert "play-after" delays node.animation.setDuration(mDuration); } } //设置所有动画的插值器 if (mInterpolator != null) { for (Node node : mNodes) { node.animation.setInterpolator(mInterpolator); } } // First, sort the nodes (if necessary). This will ensure that sortedNodes // contains the animation nodes in the correct order. //对所有的动画节点进行排序 sortNodes();
//清除老的动画监听 intnumSortedNodes= mSortedNodes.size(); for (inti=0; i < numSortedNodes; ++i) { Nodenode= mSortedNodes.get(i); // First, clear out the old listeners ArrayList<AnimatorListener> oldListeners = node.animation.getListeners(); if (oldListeners != null && oldListeners.size() > 0) { final ArrayList<AnimatorListener> clonedListeners = new ArrayList<AnimatorListener>(oldListeners);
for (AnimatorListener listener : clonedListeners) { if (listener instanceof DependencyListener || listener instanceof AnimatorSetListener) { node.animation.removeListener(listener); } } } }
// nodesToStart holds the list of nodes to be started immediately. We don't want to // start the animations in the loop directly because we first need to set up // dependencies on all of the nodes. For example, we don't want to start an animation // when some other animation also wants to start when the first animation begins. final ArrayList<Node> nodesToStart = newArrayList<Node>(); for (inti=0; i < numSortedNodes; ++i) { Nodenode= mSortedNodes.get(i); if (mSetListener == null) { mSetListener = newAnimatorSetListener(this); } //动画节点没有依赖别的节点,添加到即将开始的队列中 if (node.dependencies == null || node.dependencies.size() == 0) { nodesToStart.add(node); } else { //如果有依赖的话,添加依赖监听,并把依赖添加到临时依赖链中 intnumDependencies= node.dependencies.size(); for (intj=0; j < numDependencies; ++j) { Dependencydependency= node.dependencies.get(j); dependency.node.animation.addListener( newDependencyListener(this, node, dependency.rule)); } node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone(); } node.animation.addListener(mSetListener); } // Now that all dependencies are set up, start the animations that should be started. //开始动画 if (mStartDelay <= 0) { for (Node node : nodesToStart) { node.animation.start(); mPlayingSet.add(node.animation); } } else { mDelayAnim = ValueAnimator.ofFloat(0f, 1f); mDelayAnim.setDuration(mStartDelay); mDelayAnim.addListener(newAnimatorListenerAdapter() { booleancanceled=false; publicvoidonAnimationCancel(Animator anim) { canceled = true; } publicvoidonAnimationEnd(Animator anim) { if (!canceled) { intnumNodes= nodesToStart.size(); for (inti=0; i < numNodes; ++i) { Nodenode= nodesToStart.get(i); node.animation.start(); mPlayingSet.add(node.animation); } } mDelayAnim = null; } }); mDelayAnim.start(); } //动画开始回调 if (mListeners != null) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); intnumListeners= tmpListeners.size(); for (inti=0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationStart(this); } } //如果没有动画,动画结束回调 if (mNodes.size() == 0 && mStartDelay == 0) { // Handle unusual case where empty AnimatorSet is started - should send out // end event immediately since the event will not be sent out at all otherwise mStarted = false; if (mListeners != null) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); intnumListeners= tmpListeners.size(); for (inti=0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationEnd(this); } } } }
动画排序 privatevoidsortNodes() { if (mNeedsSort) { mSortedNodes.clear(); ArrayList<Node> roots = newArrayList<Node>(); intnumNodes= mNodes.size(); //没有依赖的动画变成根节点 for (inti=0; i < numNodes; ++i) { Nodenode= mNodes.get(i); if (node.dependencies == null || node.dependencies.size() == 0) { roots.add(node); } } ArrayList<Node> tmpRoots = newArrayList<Node>(); while (roots.size() > 0) { //遍历每个根节点 intnumRoots= roots.size(); for (inti=0; i < numRoots; ++i) { Noderoot= roots.get(i); mSortedNodes.add(root); if (root.nodeDependents != null) { intnumDependents= root.nodeDependents.size(); //根节点有依赖 for (intj=0; j < numDependents; ++j) { Nodenode= root.nodeDependents.get(j); //将此根节点从依赖中移除node.nodeDependencies.remove(root); if (node.nodeDependencies.size() == 0) { tmpRoots.add(node); } } } } roots.clear(); roots.addAll(tmpRoots); tmpRoots.clear(); } mNeedsSort = false; if (mSortedNodes.size() != mNodes.size()) { thrownewIllegalStateException("Circular dependencies cannot exist" + " in AnimatorSet"); } } else { // Doesn't need sorting, but still need to add in the nodeDependencies list // because these get removed as the event listeners fire and the dependencies // are satisfied intnumNodes= mNodes.size(); for (inti=0; i < numNodes; ++i) { Nodenode= mNodes.get(i); if (node.dependencies != null && node.dependencies.size() > 0) { intnumDependencies= node.dependencies.size(); for (intj=0; j < numDependencies; ++j) { Dependencydependency= node.dependencies.get(j); if (node.nodeDependencies == null) { node.nodeDependencies = newArrayList<Node>(); } if (!node.nodeDependencies.contains(dependency.node)) { node.nodeDependencies.add(dependency.node); } } } // nodes are 'done' by default; they become un-done when started, and done // again when ended node.done = false; } } } ``` * 动画排序规则: * 如果需要排序 1. 没有依赖的动画变成根节点. 2. 循环遍历所有根节点,将根节点从依赖节点中移除 * 不需要排序 1. 添加回依赖
##### Node 代表动画和其依赖。其包含一个动画信息,节点动画依赖集合,独立节点集合
##### Dependency 动画节点与节点之间的依赖描述
```java staticfinalintWITH=0; // dependent node must start with this dependency node staticfinalintAFTER=1; // dependent node must start when this dependency node finishes
// If we already have a listener for this child, then we've already set up the // changing animation we need. Multiple calls for a child may occur when several // add/remove operations are run at once on a container; each one will trigger // changes for the existing children in the container. if (layoutChangeListenerMap.get(child) != null) { return; }
// Don't animate items up from size(0,0); this is likely because the objects // were offscreen/invisible or otherwise measured to be infinitely small. We don't // want to see them animate into their real size; just ignore animation requests // on these views if (child.getWidth() == 0 && child.getHeight() == 0) { return; }
// Make a copy of the appropriate animation finalAnimatoranim= baseAnimator.clone();
// Set the target object for the animation anim.setTarget(child);
// A ObjectAnimator (or AnimatorSet of them) can extract start values from // its target object anim.setupStartValues();
// If there's an animation running on this view already, cancel it AnimatorcurrentAnimation= pendingAnimations.get(child); if (currentAnimation != null) { currentAnimation.cancel(); pendingAnimations.remove(child); } // Cache the animation in case we need to cancel it later pendingAnimations.put(child, anim);
// For the animations which don't get started, we have to have a means of // removing them from the cache, lest we leak them and their target objects. // We run an animator for the default duration+100 (an arbitrary time, but one // which should far surpass the delay between setting them up here and // handling layout events which start them. ValueAnimatorpendingAnimRemover= ValueAnimator.ofFloat(0f, 1f). setDuration(duration + 100); pendingAnimRemover.addListener(newAnimatorListenerAdapter() { @Override publicvoidonAnimationEnd(Animator animation) { pendingAnimations.remove(child); } }); pendingAnimRemover.start();
// Add a listener to track layout changes on this view. If we don't get a callback, // then there's nothing to animate. final View.OnLayoutChangeListenerlistener=newView.OnLayoutChangeListener() { publicvoidonLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Tell the animation to extract end values from the changed object anim.setupEndValues(); if (anim instanceof ValueAnimator) { booleanvaluesDiffer=false; ValueAnimatorvalueAnim= (ValueAnimator)anim; PropertyValuesHolder[] oldValues = valueAnim.getValues(); for (inti=0; i < oldValues.length; ++i) { PropertyValuesHolderpvh= oldValues[i]; if (pvh.mKeyframes instanceof KeyframeSet) { KeyframeSetkeyframeSet= (KeyframeSet) pvh.mKeyframes; if (keyframeSet.mFirstKeyframe == null || keyframeSet.mLastKeyframe == null || !keyframeSet.mFirstKeyframe.getValue().equals( keyframeSet.mLastKeyframe.getValue())) { valuesDiffer = true; } } elseif (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) { valuesDiffer = true; } } if (!valuesDiffer) { return; } }
AnimatorprevAnimation= currentChangingAnimations.get(child); if (prevAnimation != null) { prevAnimation.cancel(); } AnimatorpendingAnimation= pendingAnimations.get(child); if (pendingAnimation != null) { pendingAnimations.remove(child); } // Cache the animation in case we need to cancel it later currentChangingAnimations.put(child, anim);
// this only removes listeners whose views changed - must clear the // other listeners later child.removeOnLayoutChangeListener(this); layoutChangeListenerMap.remove(child); } }; // Remove the animation from the cache when it ends anim.addListener(newAnimatorListenerAdapter() {
privatevoidanimateProperty(int constantName, float toValue) { floatfromValue= getValue(constantName); floatdeltaValue= toValue - fromValue; animatePropertyBy(constantName, fromValue, deltaValue); } privatevoidanimatePropertyBy(int constantName, float startValue, float byValue) { // First, cancel any existing animations on this property if (mAnimatorMap.size() > 0) { AnimatoranimatorToCancel=null; Set<Animator> animatorSet = mAnimatorMap.keySet(); for (Animator runningAnim : animatorSet) { PropertyBundlebundle= mAnimatorMap.get(runningAnim); if (bundle.cancel(constantName)) { // property was canceled - cancel the animation if it's now empty // Note that it's safe to break out here because every new animation // on a property will cancel a previous animation on that property, so // there can only ever be one such animation running. if (bundle.mPropertyMask == NONE) { // the animation is no longer changing anything - cancel it animatorToCancel = runningAnim; break; } } } if (animatorToCancel != null) { animatorToCancel.cancel(); } }
// Capture current values ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
if (runningTransitions != null && runningTransitions.size() > 0) { for (Transition runningTransition : runningTransitions) { runningTransition.pause(sceneRoot); } }
if (transition != null) { transition.captureValues(sceneRoot, true); }
// Notify previous scene that it is being exited ScenepreviousScene= Scene.getCurrentScene(sceneRoot); if (previousScene != null) { previousScene.exit(); } }
protectedvoidcreateAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, ArrayList<TransitionValues> endValuesList) { longstartDelay= getStartDelay(); intnumTransitions= mTransitions.size(); for (inti=0; i < numTransitions; i++) { TransitionchildTransition= mTransitions.get(i); // We only set the start delay on the first transition if we are playing // the transitions sequentially. if (startDelay > 0 && (mPlayTogether || i == 0)) { longchildStartDelay= childTransition.getStartDelay(); if (childStartDelay > 0) { childTransition.setStartDelay(startDelay + childStartDelay); } else { childTransition.setStartDelay(startDelay); } } childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList, endValuesList); } }
@Override protectedvoidrunAnimators() { if (mTransitions.isEmpty()) { start(); end(); return; } setupStartEndListeners(); intnumTransitions= mTransitions.size(); if (!mPlayTogether) { //串行播放动画 // Setup sequence with listeners // TODO: Need to add listeners in such a way that we can remove them later if canceled for (inti=1; i < numTransitions; ++i) { TransitionpreviousTransition= mTransitions.get(i - 1); finalTransitionnextTransition= mTransitions.get(i); previousTransition.addListener(newTransitionListenerAdapter() { @Override publicvoidonTransitionEnd(Transition transition) { nextTransition.runAnimators(); transition.removeListener(this); } }); } TransitionfirstTransition= mTransitions.get(0); if (firstTransition != null) { firstTransition.runAnimators(); } } else { //并行播放动画 for (inti=0; i < numTransitions; ++i) { mTransitions.get(i).runAnimators(); } } }
/** * Provide a Path to interpolate between two points <code>(startX, startY)</code> and * <code>(endX, endY)</code>. This allows controlled curved motion along two dimensions. * * @param startX The x coordinate of the starting point. * @param startY The y coordinate of the starting point. * @param endX The x coordinate of the ending point. * @param endY The y coordinate of the ending point. * @return A Path along which the points should be interpolated. The returned Path * must start at point <code>(startX, startY)</code>, typically using * {@link android.graphics.Path#moveTo(float, float)} and end at <code>(endX, endY)</code>. */ publicabstract Path getPath(float startX, float startY, float endX, float endY); }
if (values != null) {
Float savedScaleX = (Float) values.values.get(PROPNAME_SCALE_X);
Float savedScaleY = (Float) values.values.get(PROPNAME_SCALE_Y);
// if saved value is not equal initial value it means that previous
// transition was interrupted and in the onTransitionEnd
// we've applied endScale. we should apply proper value to
// continue animation from the interrupted state
if (savedScaleX != null && savedScaleX != initialScaleX) {
startScaleX = savedScaleX;
}
if (savedScaleY != null && savedScaleY != initialScaleY) {
startScaleY = savedScaleY;
}
}
view.setScaleX(startScaleX);
view.setScaleY(startScaleY);
Animator animator = TransitionUtils.mergeAnimators(
ObjectAnimator.ofFloat(view, View.SCALE_X, startScaleX, endScaleX),
ObjectAnimator.ofFloat(view, View.SCALE_Y, startScaleY, endScaleY));
addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
view.setScaleX(initialScaleX);
view.setScaleY(initialScaleY);
}
});
return animator;
// If there was no Bitmap then we need to decode it from the stream. if (bitmap == null) { //从流中读取 InputStreamis= result.getStream(); try { bitmap = decodeStream(is, data); } finally { Utils.closeQuietly(is); } } }
if (bitmap != null) { if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId()); } stats.dispatchBitmapDecoded(bitmap); if (data.needsTransformation() || exifOrientation != 0) { //bitmap 转换 synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifOrientation != 0) { bitmap = transformResult(data, bitmap, exifOrientation); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId()); } } if (data.hasCustomTransformations()) { bitmap = applyCustomTransformations(data.transformations, bitmap); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations"); } } } if (bitmap != null) { //更新状态 stats.dispatchBitmapTransformed(bitmap); } } }
public ServiceMethod build() { callAdapter = createCallAdapter(); responseType = callAdapter.responseType(); if (responseType == Response.class || responseType == okhttp3.Response.class) { throw methodError("'" + Utils.getRawType(responseType).getName() + "' is not a valid response body type. Did you mean ResponseBody?"); } responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); }
if (httpMethod == null) { throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.)."); }
if (!hasBody) { if (isMultipart) { throw methodError( "Multipart can only be specified on HTTP methods with request body (e.g., @POST)."); } if (isFormEncoded) { throw methodError("FormUrlEncoded can only be specified on HTTP methods with " + "request body (e.g., @POST)."); } }
intparameterCount= parameterAnnotationsArray.length; parameterHandlers = newParameterHandler<?>[parameterCount]; for (intp=0; p < parameterCount; p++) { TypeparameterType= parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); }
Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); }
if (relativeUrl == null && !gotUrl) { throw methodError("Missing either @%s URL or @Url parameter.", httpMethod); } if (!isFormEncoded && !isMultipart && !hasBody && gotBody) { throw methodError("Non-body HTTP method cannot contain @Body."); } if (isFormEncoded && !gotField) { throw methodError("Form-encoded method must contain at least one @Field."); } if (isMultipart && !gotPart) { throw methodError("Multipart method must contain at least one @Part."); }
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF. env->ExceptionClear(); return env->NewStringUTF(error_msg.c_str()); }
// See if we've already loaded this library. If we have, and the class loader // matches, return successfully without doing anything. // TODO: for better results we should canonicalize the pathname (or even compare // inodes). This implementation is fine if everybody is using System.loadLibrary. SharedLibrary* library; Thread* self = Thread::Current(); { // TODO: move the locking (and more of this logic) into Libraries. MutexLock mu(self, *Locks::jni_libraries_lock_); library = libraries_->Get(path); } if (library != nullptr) { if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) { // The library will be associated with class_loader. The JNI // spec says we can't load the same library into more than one // class loader. StringAppendF(error_msg, "Shared library \"%s\" already opened by " "ClassLoader %p; can't open in ClassLoader %p", path.c_str(), library->GetClassLoader(), class_loader); LOG(WARNING) << error_msg; return false; } VLOG(jni) << "[Shared library \"" << path << "\" already loaded in " << " ClassLoader " << class_loader << "]"; if (!library->CheckOnLoadResult()) { StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt " "to load \"%s\"", path.c_str()); return false; } return true; }
// Open the shared library. Because we're using a full path, the system // doesn't have to search through LD_LIBRARY_PATH. (It may do so to // resolve this library's dependencies though.)
// Failures here are expected when java.library.path has several entries // and we have to hunt for the lib.
// Below we dlopen but there is no paired dlclose, this would be necessary if we supported // class unloading. Libraries will only be unloaded when the reference count (incremented by // dlopen) becomes zero from dlclose.
if (env->ExceptionCheck() == JNI_TRUE) { LOG(ERROR) << "Unexpected exception:"; env->ExceptionDescribe(); env->ExceptionClear(); } // Create a new entry. // TODO: move the locking (and more of this logic) into Libraries. bool created_library = false; { // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering. std::unique_ptr<SharedLibrary> new_library( new SharedLibrary(env, self, path, handle, class_loader)); MutexLock mu(self, *Locks::jni_libraries_lock_); library = libraries_->Get(path); if (library == nullptr) { // We won race to get libraries_lock. library = new_library.release(); libraries_->Put(path, library); created_library = true; } } if (!created_library) { LOG(INFO) << "WOW: we lost a race to add shared library: " << "\"" << path << "\" ClassLoader=" << class_loader; return library->CheckOnLoadResult(); } VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
bool was_successful = false; void* sym; if (needs_native_bridge) { library->SetNeedsNativeBridge(); sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr); } else { sym = dlsym(handle, "JNI_OnLoad"); } if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; was_successful = true; } else { // Call JNI_OnLoad. We have to override the current class // loader, which will always be "null" since the stuff at the // top of the stack is around Runtime.loadLibrary(). (See // the comments in the JNI FindClass function.) ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader);
VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; typedef int (*JNI_OnLoadFn)(JavaVM*, void*); JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); int version = (*jni_on_load)(this, nullptr);
if (version == JNI_ERR) { StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); } else if (IsBadJniVersion(version)) { StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", path.c_str(), version); // It's unwise to call dlclose() here, but we can mark it // as bad and ensure that future load attempts will fail. // We don't know how far JNI_OnLoad got, so there could // be some partially-initialized stuff accessible through // newly-registered native method calls. We could try to // unregister them, but that doesn't seem worthwhile. } else { was_successful = true; } VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure") << " from JNI_OnLoad in \"" << path << "\"]"; }
publicvoidaddView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { thrownewIllegalArgumentException("view must not be null"); } if (display == null) { thrownewIllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { thrownewIllegalArgumentException("Params must be WindowManager.LayoutParams"); }
final WindowManager.LayoutParamswparams= (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. finalContextcontext= view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } }
ViewRootImpl root; ViewpanelParentView=null;
synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = newRunnable() { @Overridepublicvoidrun() { synchronized (mLock) { for (inti= mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); }
intindex= findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { thrownewIllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. }
// If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { finalintcount= mViews.size(); for (inti=0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } }
// do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { finalintindex= findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
// Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); privatestaticintgetRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
mInLayout = false; intnumViewsRequestingLayout= mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. // If no layout-request flags are set on the requesting views, there is no problem. // If some requests are still pending, then we need to clear those flags and do // a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false); if (validLayoutRequesters != null) { // Set this flag to indicate that any further requests are happening during // the second pass, which may result in posting those requests to the next // frame instead mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout intnumValidRequests= validLayoutRequesters.size(); for (inti=0; i < numValidRequests; ++i) { finalViewview= validLayoutRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the // layout flags, since requests happening during the second pass get noop'd validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true); if (validLayoutRequesters != null) { final ArrayList<View> finalRequesters = validLayoutRequesters; // Post second-pass requests to the next frame getRunQueue().post(newRunnable() { @Override publicvoidrun() { intnumValidRequests= finalRequesters.size(); for (inti=0; i < numValidRequests; ++i) { finalViewview= finalRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); } } }); } }
// For whatever reason we didn't create a HardwareRenderer, end any // hardware animations that are now dangling if (mAttachInfo.mPendingAnimatingRenderNodes != null) { finalintcount= mAttachInfo.mPendingAnimatingRenderNodes.size(); for (inti=0; i < count; i++) { mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators(); } mAttachInfo.mPendingAnimatingRenderNodes.clear(); }
if (mReportNextDraw) { mReportNextDraw = false; if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.fence(); }
if (LOCAL_LOGV) { Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); } if (mSurfaceHolder != null && mSurface.isValid()) { mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( mSurfaceHolder); } } } } try { mWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { } } } 调用draw()方法 如果开启了硬件加速,使用ThreadedRender.draw绘制,否则采用传统canvas绘制
// Grab a copy of everything we need CanvasContext* context = mContext;
// From this point on anything in "this" is *UNSAFE TO ACCESS* if (canUnblockUiThread) { unblockUiThread(); }
if (CC_LIKELY(canDrawThisFrame)) { context->draw(); }
if (!canUnblockUiThread) { unblockUiThread(); } }
CanvasContext
voidCanvasContext::draw() { LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE, "drawRenderNode called on a context with no canvas or surface!");
SkRect dirty; mDamageAccumulator.finish(&dirty);
// TODO: Re-enable after figuring out cause of b/22592975 // if (dirty.isEmpty() && Properties::skipEmptyFrames) { // mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); // return; // }
Rect outBounds; mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
profiler().draw(mCanvas);
bool drew = mCanvas->finish();
// Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point mCurrentFrameInfo->markSwapBuffers();
if (drew) { swapBuffers(dirty, width, height); }
// TODO: Use a fence for real completion? mCurrentFrameInfo->markFrameCompleted(); mJankTracker.addFrame(*mCurrentFrameInfo); mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); }
static abstract interface ServiceFetcher<T> { T getService(ContextImpl ctx); } static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> { private final int mCacheIndex;
public CachedServiceFetcher() { mCacheIndex = sServiceCacheSize++; }
@Override @SuppressWarnings("unchecked") public final T getService(ContextImpl ctx) { final Object[] cache = ctx.mServiceCache; synchronized (cache) { // Fetch or create the service. Object service = cache[mCacheIndex]; if (service == null) { service = createService(ctx); cache[mCacheIndex] = service; } return (T)service; } }
public abstract T createService(ContextImpl ctx); }
这里每次获取服务的时候都会进行缓存检查确保只会存在一个相同的服务
下面是uml图
1 2 3 4 5 6
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); }});
final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root;
try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty }
if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); }
final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); }
if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); }
rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } }
if (DEBUG) { System.out.println("-----> start inflating children"); }
// Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true);
if (DEBUG) { System.out.println("-----> done inflating children"); }
// We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); }
// Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } }
} catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (Exception e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; }
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); }
// Apply a theme wrapper, if allowed and one is specified. if (!ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); }
if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); }
if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { // If we have a filter, apply it to cached constructor if (mFilter != null) { // Have we seen this name before? Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // New class -- remember whether it is allowed clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); boolean allowed = clazz != null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); if (!allowed) { failNotAllowed(name, prefix, attrs); } } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, attrs); } } }
final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view;
final Retrofit loRetrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .addConverterFactory(JacksonConverterFactory.create()) // add the Jackson specific converter .build();
final GitHubService loService = loRetrofit.create(GitHubService.class); return loService;
final Configuration loConfiguration = new Configuration.Builder(poContext) .minConsumerCount(1) // always keep at least one consumer alive .maxConsumerCount(3) // up to 3 consumers at a time .loadFactor(3) // 3 jobs per consumer .consumerKeepAlive(120) // wait 2 minute .build();
final JobManager loJobManager = new JobManager(poContext, loConfiguration);
public abstract class AbstractQuery extends Job { private static final String TAG = AbstractQuery.class.getSimpleName(); private static final boolean DEBUG = true;
protected enum Priority { LOW(0), MEDIUM(500), HIGH(1000); private final int value;
Priority(final int piValue) { value = piValue; } }
protected AbstractQuery(final Priority poPriority, final boolean pbPersistent, final String psGroupId, final long plDelayMs) { super(new Params(poPriority.value).requireNetwork().setPersistent(pbPersistent).setGroupId(psGroupId).setDelayMs(plDelayMs)); } //endregion
//region Overridden methods @Override public void onAdded() { }
public class QueryGetRepos extends AbstractQuery { private static final String TAG = QueryGetRepos.class.getSimpleName(); private static final boolean DEBUG = true;
//region Fields public final String user; //endregion
//region Constructor matching super protected QueryGetRepos(@NonNull final String psUser) { super(Priority.MEDIUM); user = psUser; } //endregion
//region Overridden method @Override protected void execute() throws Exception { final GitHubService gitHubService = // specific code to get GitHubService instance
final Call<List<DTORepo>> loCall = gitHubService.listRepos(user); final Response<List<DTORepo>> loExecute = loCall.execute(); final List<DTORepo> loBody = loExecute.body();
// TODO deal with list of DTORepo }
@Override protected void postEventQueryFinished() { final EventQueryGetRepos loEvent = new EventQueryGetRepos(this, mSuccess, mErrorType, mThrowable); busManager.postEventOnMainThread(loEvent); }
@Override public void postEventQueryFinishedNoNetwork() { final EventQueryGetRepos loEvent = new EventQueryGetRepos(this, false, AbstractEventQueryDidFinish.ErrorType.NETWORK_UNREACHABLE, null); busManager.postEventOnMainThread(loEvent); } //endregion
//region Dedicated EventQueryDidFinish public static final class EventQueryGetRepos extends AbstractEventQueryDidFinish<QueryGetRepos> { public EventQueryGetRepos(final QueryGetRepos poQuery, final boolean pbSuccess, final ErrorType poErrorType, final Throwable poThrowable) { super(poQuery, pbSuccess, poErrorType, poThrowable); } } //endregion }
现在,通过QueryFactor这个简单的代理类来进行请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
public class QueryFactory { //region Build methods public QueryGetRepos buildQueryGetRepos(@NonNull final String psUser) { return new QueryGetRepos(psUser); } //endregion
//region Start methods public void startQuery(@NonNull final Context poContext, @NonNull final AbstractQuery poQuery) { final Intent loIntent = new ServiceQueryExecutorIntentBuilder(poQuery).build(poContext); poContext.startService(loIntent); }
public void startQueryGetRepos(@NonNull final Context poContext, @NonNull final String psUser) { final QueryGetRepos loQuery = buildQueryGetRepos(psUser); startQuery(poContext, loQuery); } //endregion }
public class ServiceQueryExecutor extends IntentService { private static final String TAG = ServiceQueryExecutor.class.getSimpleName();
//region Extra fields AbstractQuery query; //endregion
MerlinsBeard merlinsBeard; JobManager jobManager;
//region Constructor matching super /** * Creates an IntentService. Invoked by your subclass's constructor. */ public ServiceQueryExecutor() { super(TAG); } //endregion
//region Overridden methods @DebugLog @Override protected void onHandleIntent(final Intent poIntent) { // TODO get AbstractQuery from Intent // TODO get MerlinsBeard and JobManager instances
// If query requires network, and if network is unreachable, and if the query must not persist if (query.requiresNetwork() && !merlinsBeard.isConnected() && !query.isPersistent()) { // then, we post an event to notify the job could not be done because of network connectivity query.postEventQueryFinishedNoNetwork(); } else { // otherwise, we can add the job jobManager.addJobInBackground(query); } } //endregion }
public abstract class AbstractOrmLiteEntity { @DatabaseField(columnName = BaseColumns._ID, generatedId = true) protected long _id;
//region Getter public long getBaseId() { return _id; } //endregion }
现在创建一个POJO类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@DatabaseTable(tableName = "REPO", daoClass = DAORepo.class) public class RepoEntity extends AbstractOrmLiteEntity { @DatabaseField public Integer id;
@DatabaseField public String name;
@DatabaseField public String location;
@DatabaseField public String url; }
我们来看一下DAO。这个设计目的在于通过抽象的接口来访问具体的数据。
OrmLite提供了Dao接口以及其实现BaseDaoImpl。CURD所需的操作都具备。
然而,这些都是同步执行的。使用RxJava来异步执行这些操作。
因此我使用RxJava重写了所有的方法 我创建了如下接口
1
public interface IRxDao<T, ID> extends Dao<T, ID>
所有的方法名以”rx”为前缀。返回一个特定类型的Observable对象
1
public abstract class RxBaseDaoImpl<DataType extends AbstractOrmLiteEntity, IdType> extends BaseDaoImpl<DataType, IdType> implements IRxDao<DataType, IdType>
使用long作为ID类型
1 2
public interface IOrmLiteEntityDAO<DataType extends AbstractOrmLiteEntity> extends Dao<DataType, Long> { }
抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public abstract class AbstractBaseDAOImpl<DataType extends AbstractOrmLiteEntity> extends RxBaseDaoImpl<DataType, Long> implements IOrmLiteEntityDAO<DataType> { //region Constructors matching super protected AbstractBaseDAOImpl(final Class<DataType> poDataClass) throws SQLException { super(poDataClass); }
public AbstractBaseDAOImpl(final ConnectionSource poConnectionSource, final Class<DataType> poDataClass) throws SQLException { super(poConnectionSource, poDataClass); }
public AbstractBaseDAOImpl(final ConnectionSource poConnectionSource, final DatabaseTableConfig<DataType> poTableConfig) throws SQLException { super(poConnectionSource, poTableConfig); } //endregion }
DAORepo变成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class DAORepo extends AbstractBaseDAOImpl<RepoEntity> { //region Constructors matching super public DAORepo(final ConnectionSource poConnectionSource) throws SQLException { this(poConnectionSource, RepoEntity.class); }
public DAORepo(final ConnectionSource poConnectionSource, final Class<RepoEntity> poDataClass) throws SQLException { super(poConnectionSource, poDataClass); }
public DAORepo(final ConnectionSource poConnectionSource, final DatabaseTableConfig<RepoEntity> poTableConfig) throws SQLException { super(poConnectionSource, poTableConfig); } //endregion }
public class DatabaseHelperAndroidStarter extends OrmLiteSqliteOpenHelper { private static final String DATABASE_NAME = "android_starter.db"; private static final int DATABASE_VERSION = 1;
//region Constructor public DatabaseHelperAndroidStarter(@NonNull final Context poContext) { super(poContext, DATABASE_NAME, null, DATABASE_VERSION); } //endregion
//region Methods to override @Override @SneakyThrows(SQLException.class) public void onCreate(@NonNull final SQLiteDatabase poDatabase, @NonNull final ConnectionSource poConnectionSource) { TableUtils.createTable(poConnectionSource, RepoEntity.class); }
@Override @SneakyThrows(SQLException.class) public void onUpgrade(@NonNull final SQLiteDatabase poDatabase, @NonNull final ConnectionSource poConnectionSource, final int piOldVersion, final int piNewVersion) { TableUtils.dropTable(poConnectionSource, RepoEntity.class, true); onCreate(poDatabase, poConnectionSource); } //endregion }
ORMLite提供TableUtils,其可以根据映射的类文件创建或者删除表
现在,我们需要一个DatabaseHelperAndroidStarter来处理数据
1 2 3
public DatabaseHelperAndroidStarter getDatabaseHelperAndroidStarter(@NonNull final Context poContext) { return new DatabaseHelperAndroidStarter(poContext); }
我们能通过下面的方式获得一个DAORepo实例
1 2 3
public DAORepo getDAORepo(@NonNull final DatabaseHelperAndroidStarter poDatabaseHelperAndroidStarter) { return new DAORepo(poDatabaseHelperAndroidStarter.getConnectionSource()); }
@Mappable(with = DTORepo.class) @DatabaseTable(tableName = "REPO", daoClass = DAORepo.class) public class RepoEntity extends AbstractOrmLiteEntity implements Serializable { @Mapped @DatabaseField public Integer id;
@Mapped @DatabaseField public String name;
@Mapped @DatabaseField public String location;
@Mapped @DatabaseField public String url; }
现在我们能通过以下方式将DTO转换成实体
1 2
final Transformer loTransformerRepo = new Transformer.Builder().build(RepoEntity.class); final RepoEntity loRepo = loTransformerRepo.transform(loDTORepo, RepoEntity.class);
@AutoInjector(ApplicationAndroidStarter.class) // to automatically add inject method in component public final class PresenterRepoDetail extends MvpBasePresenter<ViewRepoDetail> {
//region Injected fields @Inject DAORepo daoRepo; // we need the DAO to load the repo from its ID //endregion
//region Fields private Subscription mSubscriptionGetRepo; // the RxJava subscription, to destroy it when needed //endregion
//region Constructor public PresenterRepoDetail() { // inject necessary fields via the component ApplicationAndroidStarter.sharedApplication().componentApplication().inject(this); } //endregion
//region Visible API public void loadRepo(final long plRepoId, final boolean pbPullToRefresh) { if (isViewAttached()) { getView().showLoading(pbPullToRefresh); } // get repo asynchronously via RxJava rxGetRepo(plRepoId); }
public void onDestroy() { // destroy the RxJava subscribtion if (mSubscriptionGetRepo != null) { mSubscriptionGetRepo.unsubscribe(); mSubscriptionGetRepo = null; } } //endregion