首页 文章 精选 留言 我的

精选列表

搜索[智能解析],共10000篇文章
优秀的个人博客,低调大师

java面试-数据库索引全面解析

索引是什么? 数据库中查找操作非常普遍,索引就是提升查找速度的一种手段。 索引分类 B+树索引它就是传统意义上的索引,它是最常用、最有效的索引。 哈希索引哈希索引是一种自适应的索引,数据库会根据表的使用情况自动生成哈希索引,我们人为是没办法干预的。 全文索引用于实现关键词搜索。但它只能根据空格分词,因此不支持中文。若要实现搜索功能,可选择lucene。 RTree索引在mysql很少使用,仅支持geometry数据类型;相对于BTREE,RTREE的优势在于范围查找。 B+树索引 数据库以页为存储单元,一个页是8K(8192Byte),一页可以存放N条记录。页在B+树中分为:数据页和索引页。B+树的高一般为2-4层,因此查找某一键值的行记录只需2-4次IO,效率较高。 聚集索引 和 非聚集索引 不管是聚集索引还是非聚集索引,它们的逻辑结构都一棵是B+树,它们的唯一区别在于: 聚集索引的数据页存放的是完整的记录;也就是说,聚集索引决定了表的物理存储顺序; 非聚集索引的数据页只存指向记录的地址信息,它真正的数据已经在聚集索引中存储了。 联合索引 和 覆盖索引 联合索引当查询条件涉及多列时,可以使用联合索引。 覆盖索引只需通过辅助索引就能获取要查询的信息,而无需再次通过聚集索引查询具体的记录信息。由于覆盖索引并不包含整行的记录,因此它的大小远远小于聚集索引。它比较适合做一些统计操作。 MyISAM索引实现 主键索引在主键索引中,索引页中存放的是主键和指向数据页的偏移量;数据页中存放的是主键和该主键所属行记录的地址空间。 辅助索引在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。 综上所述,在MyISAM中,索引文件和数据文件分开存放,不管是主键索引还是辅助索引,都属于非聚集索引。 InnoDB索引实现 主键索引索引页仍然存放主键和和指向数据页的偏移量,但数据页存放的是完整的记录。也就是在InnoDB中,数据和主键索引是存放在一起的。 辅助索引索引节点存放的内容一样,仍然是键值信息和指向数据页的偏移量;但数据页中存放的是键值信息和该键值对应的主键。然后通过主键查询主键索引就能找到该条记录。 综上所述: 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。 InnoDB的辅助索引也会包含主键列,所以,如果主键定义的比较大,其他索引也将很大。如果想在表上定义 、很多索引,则争取尽量把主键定义得小一些。InnoDB 不会压缩索引。 索引的优点 第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 索引的缺点 第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 哪些情况需要加索引? 在经常需要搜索的列上,可以加快搜索的速度; 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构; 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度; 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的; 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间; 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。 哪些情况不需要加索引? 第一,对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。 第二,对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。 第三,对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。第四,当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。

优秀的个人博客,低调大师

onAttachedToWindow和onDetachedFromWindow调用时机源码解析

终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 先上测试代码: MyView.java import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.widget.TextView; public class MyView extends TextView { public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); Log.e("test","view constructor"); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Log.e("test", "onAttachedToWindow"); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); Log.e("test", "onDetachedFromWindow"); } } MainActivity.java import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("test", "before setContextView"); setContentView(R.layout.activity_main); Log.e("test", "after setContextView"); } @Override protected void onResume() { super.onResume(); Log.e("test", "onResume"); } @Override protected void onDestroy() { super.onDestroy(); Log.e("test", "onDestroy"); } } 运行后输出的Log如下: 点击返回键退出后,输出的Log如下: 根据Log的onAttachedToWindow和onDetachedFromWindow的输出情况一目了然。 下面通过源码分析下,他两的调用时机到底在哪。 首先看下onAttachedToWindow的调用时机,在Android源码中onResume调用前会先调用了ActivityThread中的handleResumeActivity,下面是相应的代码: ActivityThread.java final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { } } if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l);//这里调用了ViewManager中的addView方法。 } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } // Get rid of anything left hanging around. cleanUpPendingRemoveWindows(r); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { if (r.newConfig != null) { r.tmpConfig.setTo(r.newConfig); if (r.overrideConfig != null) { r.tmpConfig.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + r.tmpConfig); performConfigurationChanged(r.activity, r.tmpConfig); freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig)); r.newConfig = null; } if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); } } r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } .... } 看代码中的wm.addView(devor,l);通过该方法将View添加到Window当中(在当前Window也就是Activity,不过Window也可以是Dialog或Toast),而wm是ViewManager类型的,查看对应代码是: package android.view; /** Interface to let you add and remove child views to an Activity. To get an instance * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. */ public interface ViewManager { /** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); } 该类是一个接口,在他下面还有一个WindowManager继承于ViewManager,而真正的实现代码在WindowManagerImpl类中,代码如下: WindowManagerImpl.java /* * @see WindowManager * @see WindowManagerGlobal * @hide */ public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); private final Display mDisplay; private final Window mParentWindow; private IBinder mDefaultToken; public WindowManagerImpl(Display display) { this(display, null); } private WindowManagerImpl(Display display, Window parentWindow) { mDisplay = display; mParentWindow = parentWindow; } public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mDisplay, parentWindow); } public WindowManagerImpl createPresentationWindowManager(Display display) { return new WindowManagerImpl(display, mParentWindow); } /** * Sets the window token to assign when none is specified by the client or * available from the parent window. * * @param token The default token to assign. */ public void setDefaultToken(IBinder token) { mDefaultToken = token; } @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // Only use the default token if we don't have a parent window. if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // Only use the default token if we don't already have a token. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } } @Override public void removeView(View view) { mGlobal.removeView(view, false); } @Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true); } @Override public Display getDefaultDisplay() { return mDisplay; } } 从中可以看到addView又调用了 WindowManagerGlobal.java类中的addView,下面看看WindowManagerGlobal.java类的源码: WindowManagerGlobal.java public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (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. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } int index = 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 { throw new IllegalStateException("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) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } } root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView);//这里调用ViewRootImpl类中的setView方法,在该方法中触发了ViewRootImpl.performTraversals() } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } } 在该方法中的root.setView(view,wparams,panelParentView)方法,调用的是ViewRootImpl类中的setView方法,正是该setView方法触发了ViewRootImpl.performTraversals()方法,也就是View绘制的起点,之后会进行measure,layout,draw三个步骤从而完成一个View的显示工作。 ViewRootImpl.java /** * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; mAttachInfo.mDisplayState = mDisplay.getState(); mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); ... mSoftInputMode = attrs.softInputMode; mWindowAttributesChanged = true; mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED; mAttachInfo.mRootView = view; mAttachInfo.mScalingRequired = mTranslator != null; mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; if (panelParentView != null) { mAttachInfo.mPanelParentWindowToken = panelParentView.getApplicationWindowToken(); } mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout();//这里开始请求view的绘制 if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } .... } } } 在setView的requestLayout方法中开始View的绘制。 ViewRootImpl.java void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }在scheduleTraversals()方法中向mChoreographer中postCallback,而具体的Runable内容在TraversalRunnable类中,该类在run函数中直接执行doTraversal()方法,可以看到在该方法中最终调用了performTraversals()开启View的绘制工作。 查看ViewRootImpl.java中的performTraversals()的源码如下: ViewRootImpl.java中的performTraversals()方法 private void performTraversals() { // cache mView since it is used so much below... final View host = mView; ... if (host == null || !mAdded) return; mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; boolean newSurface = false; boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; int desiredWindowHeight; final int viewVisibility = getHostVisibility(); boolean viewVisibilityChanged = mViewVisibility != viewVisibility || mNewSurfaceNeeded; WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; surfaceChanged = true; params = lp; } CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { params = lp; mFullRedrawNeeded = true; mLayoutRequested = true; if (mLastInCompatMode) { params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = false; } else { params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = true; } } mWindowAttributesChangesFlag = 0; Rect frame = mWinFrame; if (mFirst) { mFullRedrawNeeded = true; mLayoutRequested = true; if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; } // We used to use the following condition to choose 32 bits drawing caches: // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888 // However, windows are now always 32 bits by default, so choose 32 bits mAttachInfo.mUse32BitDrawingCache = true; mAttachInfo.mHasWindowFocus = false; mAttachInfo.mWindowVisibility = viewVisibility; mAttachInfo.mRecomputeGlobalAttributes = false; viewVisibilityChanged = false; mLastConfiguration.setTo(host.getResources().getConfiguration()); mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; // Set the layout direction if it has not been set before (inherit is the default) if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) { host.setLayoutDirection(mLastConfiguration.getLayoutDirection()); } host.dispatchAttachedToWindow(mAttachInfo, 0);//这里调用了View的dispatchAttachedToWindow,也就是这里回调了onAttachedToWindow方法。 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { if (DEBUG_ORIENTATION) Log.v(TAG, "View " + host + " resized to: " + frame); mFullRedrawNeeded = true; mLayoutRequested = true; windowSizeMayChange = true; } } ... } 在该方法中调用了host.dispatchAttachedToWindow(mAttachInfo, 0);方法。host是上面传下来的DecodView,该类继承与FrameLayout类,也就是ViewGroup的子类,所以先调用的是ViewGroup中的dispatchAttachedToWindow,其代码如下: ViewGroup.java @Override void dispatchAttachedToWindow(AttachInfo info, int visibility) { mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; super.dispatchAttachedToWindow(info, visibility);//这里先调用父类,也就是View的dispathcAttachedToWindow。 mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility()));//这里调用子View的dispatchAttachedToWindow } final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility())); } } 下面查看对应的View类中的dispatchAttacToWindow。代码如下: View.java void dispatchAttachedToWindow(AttachInfo info, int visibility) { //System.out.println("Attached! " + this); mAttachInfo = info; if (mOverlay != null) { mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility); } mWindowAttachCount++; // We will need to evaluate the drawable state at least once. mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY; if (mFloatingTreeObserver != null) { info.mTreeObserver.merge(mFloatingTreeObserver); mFloatingTreeObserver = null; } if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) { mAttachInfo.mScrollContainers.add(this); mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; } performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow();//快看,快看,在这里!终于找到这个方法调用的位置了 ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. for (OnAttachStateChangeListener listener : listeners) { listener.onViewAttachedToWindow(this);// } } int vis = info.mWindowVisibility; if (vis != GONE) { onWindowVisibilityChanged(vis); } // Send onVisibilityChanged directly instead of dispatchVisibilityChanged. // As all views in the subtree will already receive dispatchAttachedToWindow // traversing the subtree again here is not desired. onVisibilityChanged(this, visibility); if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) { // If nobody has evaluated the drawable state yet, then do it now. refreshDrawableState(); } needGlobalAttributesUpdate(false); } 从上面代码可以看出一个布局的onAttachedToWindow会先调用自己的,然后再调用自己孩子的。而且从View.java的代码中也可以看出onAttachedToWindow和View自身的visibility无关,即使visibility==GONE,该方法也会调用。 好,下面来分析下onDetachedFromWindow方法的调用时机。在onDestory调用前会调用ActivityThread.java中的handleDestroyActivity方法,贴出代码: ActivityThread.java private void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { cleanUpPendingRemoveWindows(r); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; if (v != null) { if (r.activity.mVisibleFromServer) { mNumVisibleActivities--; } IBinder wtoken = v.getWindowToken(); if (r.activity.mWindowAdded) { if (r.onlyLocalRequest) { // Hold off on removing this until the new activity's // window is being added. r.mPendingRemoveWindow = v; r.mPendingRemoveWindowManager = wm; } else { wm.removeViewImmediate(v);//看这里,看这里 } } if (wtoken != null && r.mPendingRemoveWindow == null) { WindowManagerGlobal.getInstance().closeAll(wtoken, r.activity.getClass().getName(), "Activity"); } r.activity.mDecor = null; } if (r.mPendingRemoveWindow == null) { // If we are delaying the removal of the activity window, then // we can't clean up all windows here. Note that we can't do // so later either, which means any windows that aren't closed // by the app will leak. Well we try to warning them a lot // about leaking windows, because that is a bug, so if they are // using this recreate facility then they get to live with leaks. WindowManagerGlobal.getInstance().closeAll(token, r.activity.getClass().getName(), "Activity"); } // Mocked out contexts won't be participating in the normal // process lifecycle, but if we're running with a proper // ApplicationContext we need to have it tear down things // cleanly. Context c = r.activity.getBaseContext(); if (c instanceof ContextImpl) { ((ContextImpl) c).scheduleFinalCleanup( r.activity.getClass().getName(), "Activity"); } } if (finishing) { try { ActivityManagerNative.getDefault().activityDestroyed(token); } catch (RemoteException ex) { // If the system process has died, it's game over for everyone. } } mSomeActivitiesChanged = true; } 看代码中的wm.removeViewImmediate方法,还是走到WindowManagerImpl类中的removeViewImmediate,代码如下: WindowManagerImpl.java @Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true); } 好熟悉啊,还是走到了WindowManagerGlobal类中的removeView,代码如下: WindowManagerGlobal.java public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); removeViewLocked(index, immediate); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) { InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } } 跟着代码继续走,到了ViewRootImpl类中的die,代码如下: ViewRootImpl.java /** * @param immediate True, do now if not in traversal. False, put on queue and do later. * @return True, request has been queued. False, request has been completed. */ boolean die(boolean immediate) { // Make sure we do execute immediately if we are in the middle of a traversal or the damage // done by dispatchDetachedFromWindow will cause havoc on return. if (immediate && !mIsInTraversal) { doDie(); return false; } if (!mIsDrawing) { destroyHardwareRenderer(); } else { Log.e(TAG, "Attempting to destroy the window while drawing!\n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); return true; } void doDie() { checkThread(); if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mRemoved) { return; } mRemoved = true; if (mAdded) { dispatchDetachedFromWindow();//看这里,看这里 } if (mAdded && !mFirst) { destroyHardwareRenderer(); if (mView != null) { int viewVisibility = mView.getVisibility(); boolean viewVisibilityChanged = mViewVisibility != viewVisibility; if (mWindowAttributesChanged || viewVisibilityChanged) { // If layout params have been changed, first give them // to the window manager to make sure it has the correct // animation info. try { if ((relayoutWindow(mWindowAttributes, viewVisibility, false) & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { mWindowSession.finishDrawing(mWindow); } } catch (RemoteException e) { } } mSurface.release(); } } mAdded = false; } WindowManagerGlobal.getInstance().doRemoveView(this); } 在doDie里面调用了dispatchDetachedFromWindow()方法,代码如下: ViewRootImpl.java void dispatchDetachedFromWindow() { if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); mView.dispatchDetachedFromWindow();//看这里,看这里 } mAccessibilityInteractionConnectionManager.ensureNoConnection(); mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager); mAccessibilityManager.removeHighTextContrastStateChangeListener( mHighContrastTextManager); removeSendWindowContentChangedCallback(); destroyHardwareRenderer(); setAccessibilityFocus(null, null); mView.assignParent(null); mView = null; mAttachInfo.mRootView = null; mSurface.release(); if (mInputQueueCallback != null && mInputQueue != null) { mInputQueueCallback.onInputQueueDestroyed(mInputQueue); mInputQueue.dispose(); mInputQueueCallback = null; mInputQueue = null; } if (mInputEventReceiver != null) { mInputEventReceiver.dispose(); mInputEventReceiver = null; } try { mWindowSession.remove(mWindow); } catch (RemoteException e) { } // Dispose the input channel after removing the window so the Window Manager // doesn't interpret the input channel being closed as an abnormal termination. if (mInputChannel != null) { mInputChannel.dispose(); mInputChannel = null; } mDisplayManager.unregisterDisplayListener(mDisplayListener); unscheduleTraversals(); } 还记着在WindowManagerGlobal里面的root.setView(view, wparams, panelParentView);调用吧,这里的mView.dispatchDetachedFromWindow();这个mView也即是上面传过来的view。也就是先看DecorView即ViewGroup里面的dispatchDetachedFromWindow,代码如下: ViewGroup.java @Override void dispatchDetachedFromWindow() { // If we still have a touch target, we are still in the process of // dispatching motion events to a child; we need to get rid of that // child to avoid dispatching events to it after the window is torn // down. To make sure we keep the child in a consistent state, we // first send it an ACTION_CANCEL motion event. cancelAndClearTouchTargets(null); // Similarly, set ACTION_EXIT to all hover targets and clear them. exitHoverTargets(); // In case view is detached while transition is running mLayoutCalledWhileSuppressed = false; // Tear down our drag tracking mDragNotifiedChildren = null; if (mCurrentDrag != null) { mCurrentDrag.recycle(); mCurrentDrag = null; } final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { children[i].dispatchDetachedFromWindow();//这里会先调子类的dispatchDetachedFromWindow } clearDisappearingChildren(); final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchDetachedFromWindow(); } super.dispatchDetachedFromWindow();//然后这里才调用自己的。 } 这之后又到View的dispatchDetachedFromWindow了,代码如下: View.java void dispatchDetachedFromWindow() { AttachInfo info = mAttachInfo; if (info != null) { int vis = info.mWindowVisibility; if (vis != GONE) { onWindowVisibilityChanged(GONE); } } onDetachedFromWindow();//绕了一大圈,还是找到你了。快看快看,揪出来了。 onDetachedFromWindowInternal(); InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { imm.onViewDetachedFromWindow(this); } ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. for (OnAttachStateChangeListener listener : listeners) { listener.onViewDetachedFromWindow(this); } } if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) { mAttachInfo.mScrollContainers.remove(this); mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED; } mAttachInfo = null; if (mOverlay != null) { mOverlay.getOverlayView().dispatchDetachedFromWindow(); } } 看代码终于找到了onDetachedFromWindow的调用地方了。 这里总结下: 1.onAttachedToWindow调用顺序:ActivityThread.handleResumeActivity->WindowManagerImpl.addView->WindowManagerGlobal.addView->ViewRootImpl.performTraversals->ViewGroup.dispatchAttachedToWindow->View.dispatchAttachedToWindow->onAttachedToWindow 2.onDetachedFromWindow调用顺序:ActivityThread.handleDestroyActivity->WindowManagerImpl.removeViewImmediate->WindowManagerGlobal.removeView->ViewRootImpl.die->ViewRootImpl.doDie->ViewRootImpl.dispatchDetachedFromWindow->ViewGroup.dispatchDetachedFromWindow->View.dispatchDetachedFromWindow->onDetachedToWindow 3.onAttachedToWindow和onDetachedFromWindow的调用与visibility无关。 4.onAttachedToWindow是先调用自己,然后调用儿子View的。onDetachedFromWindow是先调用儿子View的,然后再调用自己的。

优秀的个人博客,低调大师

阿里自研UED计件平台技术解析

从天猫到菜鸟,从双11到造物节,在阿里巴巴,除了来自集团的UED团队外,还有一支700余人的外包团队,一起创造着阿里视觉神话。阿里的UED外包同学们每年会收到来自阿里巴巴集团50多个业务方提出的十余万外包需求。如何协调外包设计师与需求方之间的配合,并且给到外包同学合理结算价格,成为阿里UED外包管理的一大难点。 以往,阿里巴巴也会用一些简单的需求分发系统来应对。但由于实际业务场景的复杂性,需求在分配上无法做到合理与精准。加上缺乏评价和追踪体系,外包同学的执行效果得不到监控,最后形成的素材也难以沉淀。最重要的是,外包需求的分发与结算步骤完全隔离,外包素材的审核与结算只能依靠财务同学在线下进行人工方式核算,审核难度与工作量可想而知。 针对上述系列痛点,阿里巴巴设计委员会设计中台项目组联合阿里巴巴信息平台,通过技术手段将原计件系统转化成阿里

优秀的个人博客,低调大师

新浪微博技术架构问题解析

你要问我新浪微博有什么技术压力。 我觉得,本质上: 第一,如何快速共享数据,如何快速的将new message在网状社区中传递开来。 第二,长期大量积累下来的数据如何分布,分散储存,保证性能。数据库的横纵切分。 第三,机房的多备份,多备份机房之间的同步策略。 第四,业务上的实时性是不是必须,是否能够根据业务的重拍,将一部分业务异步化,减轻系统压力。 第五,高并发的请求重压之下,如何能够保证相应速度。 新浪微博首席架构师杨卫华的讲座上很好的解答了以上的这些问题。 具体参见以下资料: http://mp.weixin.qq.com/s?__biz=MzA4NDc2MDQ1Nw==&mid=2650237833&idx=2&sn=a02e5ced24f6ea7141904324dd534627#rd 问题:[第一,如何快速共享数据,如何快速的将new message在网状社区中传递开来。] 回答:采取push的方法,有选择性的,向长期活跃的用户push new message。 手机上的app中,每个app都会和服务器建立长连接,一旦平台API中有new message,就会实时推送给app 问题:[第二,长期大量积累下来的数据如何分布,分散储存,保证性能。数据库的横纵切分。] 回答:weibo是非常有时效性的业务数据。绝大多数的数据请求都集中在最近,所以数据可以按月份横切分。同时可以在月份横切的基础上建立二级索引表。 同时,绝大多数的请求和发帖都集中在单一的省份。因此在纵切分上,在每省都建立了独立的数据中心。 问题:[第三,机房的多备份,多备份机房之间的同步策略。] 回答: 每个机房都不是单点部署,单个机房的数据更新之后,都会广播到全部的所有机房,进行快速同步。 问题:[第四,业务上的实时性是不是必须,是否能够根据业务的重拍,将一部分业务异步化,减轻系统压力。] 回答:发weibo本身这个事情,需要经过漫长的事务链条,所以我们可以打断这个链条,将其异步化,在接收到weibo以后将其推入messageQueue,然后让其异步更新处理。 问题:[第五,高并发的请求重压之下,如何能够保证相应速度。] 回答:广泛的应用缓存,cache,memcached,内存数据库,将数据放在离CPU越近的地方越好。 本文转自 www19 51CTO博客,原文链接:http://blog.51cto.com/doujh/1796093,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android 卡顿优化 1 卡顿解析

1, 感知卡顿 用户对卡顿的感知, 主要来源于界面的刷新. 而界面的性能主要是依赖于设备的UI渲染性能. 如果我们的UI设计过于复杂, 或是实现不够好, 设备又不给力, 界面就会像卡住了一样, 给用户卡顿的感觉. 1.1 16ms原则 在剖析卡顿的原因之前, 我们先来了解下Android中著名的"16ms"原则: Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity). 为什么是16ms, 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次. 就像是这样的: 16ms 这就意味着, 我们需要在16ms内完成下一次要刷新的界面的相关运算, 以便界面刷新更新. 然而, 如果我们无法在16ms内完成此次运算会怎样呢? 例如, 假设我们更新屏幕的背景图片, 需要24ms来做这次运算. 当系统在第一个16ms时刷新界面, 然而我们的运算还没有结束, 无法绘出图片. 当系统隔16ms再发一次VSYNC信息重绘界面时, 用户才会看到更新后的图片. 也就是说用户是32ms后看到了这次刷新(注意, 并不是24ms). 这就是传说中的丢帧(dropped frame): dropped frame 丢帧给用户的感觉就是卡顿, 而且如果运算过于复杂, 丢帧会更多, 导致界面常常处于停滞状态, 卡到爆. 那么会有哪些常见的情况会导致运算超过16ms, 进而丢帧, 让用户觉得卡顿呢? 2, 卡顿原因分析及处理 一般来说, 会有以下几种情况导致卡顿这种性能问题, 我们逐一看下: 2.1 过于复杂的布局 上节有说, 界面性能取决于UI渲染性能. 我们可以理解为UI渲染的整个过程是由CPU和GPU两个部分协同完成的. 其中, CPU负责UI布局元素的Measure, Layout, Draw等相关运算执行. GPU负责栅格化(rasterization), 将UI元素绘制到屏幕上. 如果我们的UI布局层次太深, 或是自定义控件的onDraw中有复杂运算, CPU的相关运算就可能大于16ms, 导致卡顿. 这个时候, 我们需要借助Hierarchy Viewer这个工具来帮我们分析布局了. Hierarchy Viewer不仅可以以图形化树状结构的形式展示出UI层级, 还对每个节点给出了三个小圆点, 以指示该元素Measure, Layout, Draw的耗时及性能. 具体请参考App优化之Layout怎么摆. 2.2 过度绘制(Overdraw) 上节说的CPU方面的, 关于GPU的绘制, 如果我们的界面存在Overdraw, 也可能导致卡顿. Overdraw: 用来描述一个像素在屏幕上多少次被重绘在一帧上. 通俗的说: 理想情况下, 每屏每帧上, 每个像素点应该只被绘制一次, 如果有多次绘制, 就是Overdraw, 过度绘制了. 2.2.1 调试Overdraw Android系统提供了可视化的方案来让我们很方便的查看overdraw的现象: 在"系统设置"-->"开发者选项"-->"调试GPU过度绘制"中开启调试: toggle GPU overdraw 此时界面可能会有五种颜色标识: overdraw indicator 原色: 没有overdraw 蓝色: 1次overdraw 绿色: 2次overdraw 粉色: 3次overdraw 红色: 4次及4次以上的overdraw 一般来说, 蓝色是可接受的, 是性能优的. 2.2.2 Overdraw的分析处理 上面有言, 所谓Overdraw, 就是在一个像素点上绘制了多次. 常见的就是: 绘制了多重背景. 绘制了不可见的UI元素. 还是以GithubApp这个App的代码为例调试, 打开应用, 展示是这样的: example-1 可以看到是中间列表这块overdraw比较严重. 查看代码发现: fragment_trending_container.xml中ViewPager设置了背景: <android.support.v4.view.ViewPager android:id="@+id/view_pager" android:background="@color/md_white_1000" android:layout_width="match_parent" android:layout_height="match_parent"/> 而ViewPager中的fragment又设置了背景: <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/refresh_layout" android:background="@color/md_white_1000" android:layout_width="match_parent" android:layout_height="match_parent"> ... </android.support.v4.widget.SwipeRefreshLayout 完整代码请查看Github上源码, 本文分析时commit截止到b01b5793. 删除外层ViewPager的背景再看: example-2 可以发现中间列表区域已经不再是红色了, 但是也没有达到蓝色这个可以接受的层级. 这是因为我们的Activity默认情况下, theme会给window设置一个纯色的背景. 因为我们这里不想使用这个默认的背景,故而给layout加了一层背景, 导致了多重绘制背景. 当然我们也可以自定义主题, 将theme的window background设置成我们想要的, 而不在布局中设置. 可以通过如下方式去掉window的背景. 设置主题: <item name="android:windowBackground">@null</item> 或是代码设置, 在onCreate中: getWindow().setBackgroundDrawable(null); 此时我们看到的效果: example-3 已基本达到优化水平. 以上旨在提供分析方法和思路. Overdraw主要原因是背景的多重绘制, 或是不可见的View在背后绘制等, 但不仅限于此. 2.3 UI线程的复杂运算 如上文ANR相关分析中就说到UI线程的复杂运算会造成UI无响应, 当然更多的是造成UI响应停滞, 卡顿. 产生ANR已经是卡顿的极致了, 具体分析可以参看App优化之ANR详解一文. 关于运算阻塞导致的卡顿的分析, 可以使用Traceview这个工具. 具体Traceview的介绍, 以及实战分析, 可以参考App优化之提升你的App启动速度之理论基础和App优化之提升你的App启动速度之实例挑战. 在这里需要提下我们在性能分析工具中提到的StrictMode. 2.3.1 StrictMode的使用 StrictMode用来基于线程或VM设置一些策略, 一旦检测到策略违例, 控制台将输出一些警告,包含一个trace信息展示你的应用在何处出现问题. 通常用来检测主线程中的磁盘读写或网络访问等耗时操作. 在Application或是Activity的onCreate中开启StrictMode: public void onCreate() { if (BuildConfig.DEBUG) { // 针对线程的相关策略 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() // or .detectAll() for all detectable problems .penaltyLog() .build()); // 针对VM的相关策略 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); } 如果你的线程出了问题, 控制台会有警告输出, 可以定位到代码. 相对简单, 在此就不多废话了. 解决UI线程的耗时操作方案, 可以参考ANR详解里面说到的那些线程模式. 2.4 频繁的GC 上面说的都是处理上的, CPU, GPU相关的. 实际上内存原因也可能会造成应用不流畅, 卡顿的. 说到这, 想起当年配台式机的三大件(CPU, 内存, 显示器)了. 貌似分析App性能也是这几大件啊 :) 为什么说频繁的GC会导致卡顿呢? 简而言之, 就是执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行, 故而如果程序频繁GC, 自然会导致界面卡顿. 以下内容参考自Android Performance Patterns:Memory Churn and Performance.需FQ 导致频繁GC有两个原因: 内存抖动(Memory Churn), 即大量的对象被创建又在短时间内马上被释放. 瞬间产生大量的对象会严重占用Young Generation的内存区域, 当达到阀值, 剩余空间不够的时候, 也会触发GC. 即使每次分配的对象需要占用很少的内存,但是他们叠加在一起会增加Heap的压力, 从而触发更多的GC. 这些GC操作可能会造成上面说到的丢帧, 如下: gc dropped frame 就会让用户感知到卡顿了. 一般来说瞬间大量产生对象一般是因为我们在代码的循环中new对象, 或是在onDraw中创建对象等. 所以说这些地方是我们尤其需要注意的... 本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/8480153.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

<Docker>01 命令参数解析

最近Docker1.0稳定版发布了,这给paas注入新鲜的血液。 Docker是一个功能强大的自动化分布式系统:大规模的Web部署、数据库集群、持续部署系统、私有PaaS、面向服务的体系结构等。 Docker是一种增加了高级API的LinuX Container(LXC)技术,提供了能够独立运行Unix进程的轻量级虚拟化解决方案。 它提供了一种在安全、可重复的环境中自动部署软件的方式。 关于Docker运行在Centos的操作,下面由cantgis进行讲解。 Cantgis的实验环境都是在Centos7种运行: 1.所有 docker 操作都需要 root 权限需要加 sudo: dockersearchcentos 2.下载服务镜像(image),挑选有Tag标示的,相对好一些。 dockerpullcentos:centos7 3.查看下载完的镜像 dockerimages 4.基于下载完的镜像创建一个Container dockerrun-i-tcentos:centos7/bin/bash 这样你就进入一个Container,一个封装新的centos7系统,exit退出这个container 5.查看所有的container dockerps-a 6.若是从私有或者公有的docker库下载相应image,采用build参数,参数【.】是配置文件请点击,DockerFile dockerbuild-tNEW_Images. 7.在当前目录根据 Dockerfile 构建容器[这条命令适用于在一个container中创建了HTTPD或者Nginx服务,-p下面映射端口发布出来。] docker run -i -t - p 2013:80 运行一个 Container,支持的参数: -dDetached 或者 daemon mode,后台运行。 -i -t开一个putty 终端,保持交互模式,这两个一般共同使用。 -e设置环境变量参数,参考Install GitLab With Docker -p [host_port]:[container_port]映射 HOST 端口到容器,方便外部访问容器内服务,host_port 可以省略. -v [host-path]:[container-path]把 HOST 文件夹挂载到 Container 用以保存数据。 --rm自动删除已运行存在的相同 IMAGE 的容器。 8.列出当前运行的容器,-a会列出所有,包括已停止的,-q只列出容器 ID。 dockerps-a-q|xargsdockerrm 这条可以删除所有未运行的容器。 10.删除指定的Container dockerrmContainerID 11.删除指定的images dockerrmiimagesID 更多可参考Docker 文档 下面这张图,实在是解释的非常好,大家还是看图吧。 本文转自cantgis 51CTO博客,原文链接:http://blog.51cto.com/cantgis/1539964 ,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android中复用问题哲理性解析

Android中列表的复用机制提高了APP的运行效率,但随之而来的复用的问题总是让程序员们头痛,一个 bug找头天也找不到。我就把自己解决这方面的经验贡献出来供大家参考: 问题1:什么是复用 复用其实指的是复用View,而绑定View的数据是变化的。 问题2:复用的原理探究 为了彻底弄清楚复用的原理,和特地写了段小程序。 Adapter代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class MyAdapter extends BaseAdapter{ @Override public int getCount(){ return 20 ; } @Override public ObjectgetItem( int position){ return null ; } @Override public long getItemId( int position){ return position; } @Override public ViewgetView( int position,ViewconvertView,ViewGroupparent){ Log.i(TAG, "aaaaaaaaaa----------getView:position=" +position+ ",convertView=" +convertView); ViewHolderholder; if (convertView== null ){ convertView=LayoutInflater.from(SimpleCheckBoxListActivity. this ).inflate(R.layout.adapter_simple_checkbox_item, null , false ); holder= new ViewHolder(); holder.tv=(TextView)convertView.findViewById(R.id.tv); holder.cb=(CheckBox)convertView.findViewById(R.id.cb); convertView.setTag(holder); } else { holder=(ViewHolder)convertView.getTag(); } holder.tv.setText( "index=" +position); Log.i(TAG, "bbbbbbbbbb----------getView:position=" +position+ ",convertView=" +convertView.toString()); //将convertView缓存起来,方便后面的分析。 itemViews.put(position,convertView); //分析当前position是否复用了之前哪个位置的view int reusePosition=analyseReusedWhichPosition(position); if (reusePosition!=- 1 ){ Log.i(TAG, "getView:位置" +position+ "复用了位置" +reusePosition+ "的view" ); } return convertView; } class ViewHolder{ TextViewtv; CheckBoxcb; } //分析当前position是否复用了之前哪个位置的view private int analyseReusedWhichPosition( int currentPosition){ ViewcurrentPositionView=itemViews.get(currentPosition); for ( int i= 0 ;i<currentPosition;i++){ ViewbeforePositionView=itemViews.get(i); if (beforePositionView== null ){ continue ; } if (beforePositionView==currentPositionView){ return i; } } return - 1 ; } } 日志分析: 1)程序初次运行 打印的日志: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 aaaaaaaaaa----------getView:position= 0 ,convertView= null bbbbbbbbbb----------getView:position= 0 ,convertView=android.widget.LinearLayout{42eceab0V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 1 ,convertView= null bbbbbbbbbb----------getView:position= 1 ,convertView=android.widget.LinearLayout{42ee4650V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 2 ,convertView= null bbbbbbbbbb----------getView:position= 2 ,convertView=android.widget.LinearLayout{42ee6140V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 3 ,convertView= null bbbbbbbbbb----------getView:position= 3 ,convertView=android.widget.LinearLayout{42ee7c10V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 4 ,convertView= null bbbbbbbbbb----------getView:position= 4 ,convertView=android.widget.LinearLayout{42ee96e0V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 5 ,convertView= null bbbbbbbbbb----------getView:position= 5 ,convertView=android.widget.LinearLayout{42eeb1e8V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 6 ,convertView= null bbbbbbbbbb----------getView:position= 6 ,convertView=android.widget.LinearLayout{42eeccb8V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 7 ,convertView= null bbbbbbbbbb----------getView:position= 7 ,convertView=android.widget.LinearLayout{42eee788V.E...........I. 0 , 0 - 0 , 0 } 2)接着向下滑动,索引0没有完全消失,索引8就出现了,这时还没有复用。 打印的日志: 1 2 aaaaaaaaaa----------getView:position= 8 ,convertView= null bbbbbbbbbb----------getView:position= 8 ,convertView=android.widget.LinearLayout{42ef5150V.E...........I. 0 , 0 - 0 , 0 } 3)位置9出现,索引0已经完全消失(复用开始出现) 打印的日志: 1 2 3 4 aaaaaaaaaa----------getView:position= 9 ,convertView=android.widget.LinearLayout{42eceab0V.E............. 0 ,- 215 - 1080 , 1 } bbbbbbbbbb----------getView:position= 9 ,convertView=android.widget.LinearLayout{42eceab0V.E............D 0 ,- 215 - 1080 , 1 } getView:位置 9 复用了位置 0 的view 可以发现索引 9 处的hashCode与索引 0 处的hashCode都是42eceab0 4)紧接着向下滚动到最后(注意是慢慢地滚动) 打印的日志: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 aaaaaaaaaa----------getView:position= 10 ,convertView=android.widget.LinearLayout{42ee4650V.E............. 0 ,- 213 - 1080 , 3 } bbbbbbbbbb----------getView:position= 10 ,convertView=android.widget.LinearLayout{42ee4650V.E............D 0 ,- 213 - 1080 , 3 } getView:位置 10 复用了位置 1 的view aaaaaaaaaa----------getView:position= 11 ,convertView=android.widget.LinearLayout{42ee6140V.E............. 0 ,- 205 - 1080 , 11 } bbbbbbbbbb----------getView:position= 11 ,convertView=android.widget.LinearLayout{42ee6140V.E............D 0 ,- 205 - 1080 , 11 } getView:位置 11 复用了位置 2 的view aaaaaaaaaa----------getView:position= 12 ,convertView=android.widget.LinearLayout{42ee7c10V.E............. 0 ,- 202 - 1080 , 14 } bbbbbbbbbb----------getView:position= 12 ,convertView=android.widget.LinearLayout{42ee7c10V.E............D 0 ,- 202 - 1080 , 14 } getView:位置 12 复用了位置 3 的view aaaaaaaaaa----------getView:position= 13 ,convertView=android.widget.LinearLayout{42ee96e0V.E............. 0 ,- 201 - 1080 , 15 } bbbbbbbbbb----------getView:position= 13 ,convertView=android.widget.LinearLayout{42ee96e0V.E............D 0 ,- 201 - 1080 , 15 } getView:位置 13 复用了位置 4 的view aaaaaaaaaa----------getView:position= 14 ,convertView=android.widget.LinearLayout{42eeb1e8V.E............. 0 ,- 188 - 1080 , 28 } bbbbbbbbbb----------getView:position= 14 ,convertView=android.widget.LinearLayout{42eeb1e8V.E............D 0 ,- 188 - 1080 , 28 } getView:位置 14 复用了位置 5 的view aaaaaaaaaa----------getView:position= 15 ,convertView=android.widget.LinearLayout{42eeccb8V.E............. 0 ,- 213 - 1080 , 3 } bbbbbbbbbb----------getView:position= 15 ,convertView=android.widget.LinearLayout{42eeccb8V.E............D 0 ,- 213 - 1080 , 3 } getView:位置 15 复用了位置 6 的view aaaaaaaaaa----------getView:position= 16 ,convertView=android.widget.LinearLayout{42eee788V.E............. 0 ,- 179 - 1080 , 37 } bbbbbbbbbb----------getView:position= 16 ,convertView=android.widget.LinearLayout{42eee788V.E............D 0 ,- 179 - 1080 , 37 } getView:位置 16 复用了位置 7 的view aaaaaaaaaa----------getView:position= 17 ,convertView=android.widget.LinearLayout{42ef5150V.E............. 0 ,- 181 - 1080 , 35 } bbbbbbbbbb----------getView:position= 17 ,convertView=android.widget.LinearLayout{42ef5150V.E............D 0 ,- 181 - 1080 , 35 } getView:位置 17 复用了位置 8 的view aaaaaaaaaa----------getView:position= 18 ,convertView=android.widget.LinearLayout{42eceab0V.E............. 0 ,- 195 - 1080 , 21 } bbbbbbbbbb----------getView:position= 18 ,convertView=android.widget.LinearLayout{42eceab0V.E............D 0 ,- 195 - 1080 , 21 } getView:位置 18 复用了位置 0 的view aaaaaaaaaa----------getView:position= 19 ,convertView=android.widget.LinearLayout{42ee4650V.E............. 0 ,- 210 - 1080 , 6 } bbbbbbbbbb----------getView:position= 19 ,convertView=android.widget.LinearLayout{42ee4650V.E............D 0 ,- 210 - 1080 , 6 } getView:位置 19 复用了位置 1 的view 可以看到向下慢慢滑动的时候,复用是很有规律的。 但是如果快速的向下滑动的时候,又发现不了什么规律:复用并非是连续的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 aaaaaaaaaa----------getView:position= 8 ,convertView=android.widget.LinearLayout{42f9a780V.E............. 0 ,- 85 - 1080 , 131 } bbbbbbbbbb----------getView:position= 8 ,convertView=android.widget.LinearLayout{42f9a780V.E............D 0 ,- 85 - 1080 , 131 } getView:位置 8 复用了位置 0 的view aaaaaaaaaa----------getView:position= 9 ,convertView=android.widget.LinearLayout{42f9f818V.E............. 0 , 384 - 1080 , 600 } bbbbbbbbbb----------getView:position= 9 ,convertView=android.widget.LinearLayout{42f9f818V.E............D 0 , 384 - 1080 , 600 } getView:位置 9 复用了位置 3 的view aaaaaaaaaa----------getView:position= 10 ,convertView=android.widget.LinearLayout{42f9dd48V.E............. 0 , 138 - 1080 , 354 } bbbbbbbbbb----------getView:position= 10 ,convertView=android.widget.LinearLayout{42f9dd48V.E............D 0 , 138 - 1080 , 354 } getView:位置 10 复用了位置 2 的view aaaaaaaaaa----------getView:position= 11 ,convertView=android.widget.LinearLayout{42f9c278V.E............. 0 ,- 108 - 1080 , 108 } bbbbbbbbbb----------getView:position= 11 ,convertView=android.widget.LinearLayout{42f9c278V.E............D 0 ,- 108 - 1080 , 108 } getView:位置 11 复用了位置 1 的view aaaaaaaaaa----------getView:position= 12 ,convertView= null bbbbbbbbbb----------getView:position= 12 ,convertView=android.widget.LinearLayout{42fad3a0V.E...........I. 0 , 0 - 0 , 0 } aaaaaaaaaa----------getView:position= 13 ,convertView=android.widget.LinearLayout{42fa2df0V.E............. 0 , 60 - 1080 , 276 } bbbbbbbbbb----------getView:position= 13 ,convertView=android.widget.LinearLayout{42fa2df0V.E............D 0 , 60 - 1080 , 276 } getView:位置 13 复用了位置 5 的view aaaaaaaaaa----------getView:position= 14 ,convertView=android.widget.LinearLayout{42fa12e8V.E............. 0 ,- 186 - 1080 , 30 } bbbbbbbbbb----------getView:position= 14 ,convertView=android.widget.LinearLayout{42fa12e8V.E............D 0 ,- 186 - 1080 , 30 } getView:位置 14 复用了位置 4 的view aaaaaaaaaa----------getView:position= 15 ,convertView=android.widget.LinearLayout{42fa48c0V.E............. 0 ,- 150 - 1080 , 66 } bbbbbbbbbb----------getView:position= 15 ,convertView=android.widget.LinearLayout{42fa48c0V.E............D 0 ,- 150 - 1080 , 66 } getView:位置 15 复用了位置 6 的view aaaaaaaaaa----------getView:position= 16 ,convertView=android.widget.LinearLayout{42f9a780V.E............. 0 , 78 - 1080 , 294 } bbbbbbbbbb----------getView:position= 16 ,convertView=android.widget.LinearLayout{42f9a780V.E............D 0 , 78 - 1080 , 294 } getView:位置 16 复用了位置 0 的view aaaaaaaaaa----------getView:position= 17 ,convertView=android.widget.LinearLayout{42f9f818V.E............. 0 , 13 - 1080 , 229 } bbbbbbbbbb----------getView:position= 17 ,convertView=android.widget.LinearLayout{42f9f818V.E............D 0 , 13 - 1080 , 229 } getView:位置 17 复用了位置 3 的view aaaaaaaaaa----------getView:position= 18 ,convertView=android.widget.LinearLayout{42f9dd48V.E............. 0 ,- 28 - 1080 , 188 } bbbbbbbbbb----------getView:position= 18 ,convertView=android.widget.LinearLayout{42f9dd48V.E............D 0 ,- 28 - 1080 , 188 } getView:位置 18 复用了位置 2 的view aaaaaaaaaa----------getView:position= 19 ,convertView=android.widget.LinearLayout{42fa6390V.E............. 0 ,- 168 - 1080 , 48 } bbbbbbbbbb----------getView:position= 19 ,convertView=android.widget.LinearLayout{42fa6390V.E............D 0 ,- 168 - 1080 , 48 } getView:位置 19 复用了位置 7 的view 5)最后,向上滚动到索引为0的位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 aaaaaaaaaa----------getView:position= 11 ,convertView=android.widget.LinearLayout{4304de70V.E............. 0 ,- 212 - 1080 , 4 } bbbbbbbbbb----------getView:position= 11 ,convertView=android.widget.LinearLayout{4304de70V.E............D 0 ,- 212 - 1080 , 4 } getView:位置 11 复用了位置 8 的view aaaaaaaaaa----------getView:position= 10 ,convertView=android.widget.LinearLayout{4303ee70V.E............. 0 , 1829 - 1080 , 2045 } bbbbbbbbbb----------getView:position= 10 ,convertView=android.widget.LinearLayout{4303ee70V.E............D 0 , 1829 - 1080 , 2045 } getView:位置 10 复用了位置 1 的view aaaaaaaaaa----------getView:position= 9 ,convertView=android.widget.LinearLayout{ 43040940 V.E............. 0 , 1829 - 1080 , 2045 } bbbbbbbbbb----------getView:position= 9 ,convertView=android.widget.LinearLayout{ 43040940 V.E............D 0 , 1829 - 1080 , 2045 } getView:位置 9 复用了位置 2 的view aaaaaaaaaa----------getView:position= 8 ,convertView=android.widget.LinearLayout{4303d378V.E............. 0 , 1830 - 1080 , 2046 } bbbbbbbbbb----------getView:position= 8 ,convertView=android.widget.LinearLayout{4303d378V.E............D 0 , 1830 - 1080 , 2046 } getView:位置 8 复用了位置 0 的view aaaaaaaaaa----------getView:position= 7 ,convertView=android.widget.LinearLayout{430474b8V.E............. 0 , 1825 - 1080 , 2041 } bbbbbbbbbb----------getView:position= 7 ,convertView=android.widget.LinearLayout{430474b8V.E............D 0 , 1825 - 1080 , 2041 } getView:位置 7 复用了位置 6 的view aaaaaaaaaa----------getView:position= 6 ,convertView=android.widget.LinearLayout{43048f88V.E............. 0 , 1824 - 1080 , 2040 } bbbbbbbbbb----------getView:position= 6 ,convertView=android.widget.LinearLayout{43048f88V.E............D 0 , 1824 - 1080 , 2040 } aaaaaaaaaa----------getView:position= 5 ,convertView=android.widget.LinearLayout{43043ee0V.E............. 0 , 1822 - 1080 , 2038 } bbbbbbbbbb----------getView:position= 5 ,convertView=android.widget.LinearLayout{43043ee0V.E............D 0 , 1822 - 1080 , 2038 } getView:位置 5 复用了位置 4 的view aaaaaaaaaa----------getView:position= 4 ,convertView=android.widget.LinearLayout{430459e8V.E............. 0 , 1823 - 1080 , 2039 } bbbbbbbbbb----------getView:position= 4 ,convertView=android.widget.LinearLayout{430459e8V.E............D 0 , 1823 - 1080 , 2039 } aaaaaaaaaa----------getView:position= 3 ,convertView=android.widget.LinearLayout{ 43042410 V.E............. 0 , 1829 - 1080 , 2045 } bbbbbbbbbb----------getView:position= 3 ,convertView=android.widget.LinearLayout{ 43042410 V.E............D 0 , 1829 - 1080 , 2045 } aaaaaaaaaa----------getView:position= 2 ,convertView=android.widget.LinearLayout{4304de70V.E............. 0 , 1826 - 1080 , 2042 } bbbbbbbbbb----------getView:position= 2 ,convertView=android.widget.LinearLayout{4304de70V.E............D 0 , 1826 - 1080 , 2042 } aaaaaaaaaa----------getView:position= 1 ,convertView=android.widget.LinearLayout{4303ee70V.E............. 0 , 1828 - 1080 , 2044 } bbbbbbbbbb----------getView:position= 1 ,convertView=android.widget.LinearLayout{4303ee70V.E............D 0 , 1828 - 1080 , 2044 } aaaaaaaaaa----------getView:position= 0 ,convertView=android.widget.LinearLayout{ 43040940 V.E............. 0 , 1788 - 1080 , 2004 } bbbbbbbbbb----------getView:position= 0 ,convertView=android.widget.LinearLayout{ 43040940 V.E............D 0 , 1788 - 1080 , 2004 } 如上所述,到底谁复用了谁是随机不定的,这个我们也没有必要去关心。我们只要知道position是不变的就行了。 另外,除了打日志。可以选中某一个位置的checkbox,然后上下滑动,如果某个checkbox也莫名的选中了,那就说明这个位置的checkbox复用了之前选中的那个checkbox。 问题3:Adapter的notifyDataSetChanged()方法作了什么事情 notifyDataSetChanged,会重新走一遍可见的position的getView方法。 问题4:复用出现的场景 1.if-else的坑:在Adapter中,如果绑定View的数据的时候如果有if判断,往往很多人忘记了加else,这是大多数复用问题出现的根源之一。在一般情况下else不写没有逻辑错误,但是在ListView复 用的情况下如果不写错误就会带来错乱的麻烦。 实际场景: 比如每个item可能有或没有图片picarrList,之前我只加了if判断,如果有图片就显示。但后来上下一滑动之后发现没有图片的item竟然也显示了其它了item的图片,于是追根溯源发现是这里的问题。 2.checkbox等的复用问题:果如下图,是一个简单的CheckBox列表 第1页刚好0-8索引,我将0索引处的checkbox设置为选中状态,然后向下滑动,发现下一个出现的checkbox(索引为10,不是9,也不一定就是10,而是索引0完全消失之后第一个出现的item)竟然也选中了。 百度了一下,可以用Map<Interger,Boolean>来记录对应position的checkbox的选中状态。而且网上 的这个Map是事先就是预订好大小的了,但实际中Map的大小是确定的。 细节1):Map<Interger,Boolean>来记录对应position的checkbox的选中状态,怎么初始化? --1-- 可以先在成员或者构造方法里实例化Map对象 1 Map<Integer,Boolean>isTitleCheckBoxSelected= new HashMap(); --2-- 在getView方法里初始化Map对象,默认checkbox都是未选中状态 1 2 3 4 5 6 7 8 if (!isTitleCheckBoxSelected.containsKey(position)){ Log.i(TAG, "bindData:initcheckbox" +position); isTitleCheckBoxSelected.put(position, false ); //如果启动了全选,则新出现的view也要选中。 if (isSelectedAllStarted){ isTitleCheckBoxSelected.put(position, true ); } } 上面的这段代码其实是非常妙的,通过contains判断,保证了初始化。如果后面操作了map, 也不会影响这段代码对map的初始化。 map这种数据结构,由于key是唯一,可以做去重操作。这一点List则不可直接做到。 细节2):响应checkbox的OnCheckedChangeListener事件,将改变后的状态保存到map中。 1 2 3 4 5 6 7 header_checkbox.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener(){ @Override public void onCheckedChanged(CompoundButtonbuttonView, boolean isChecked){ if (isCheckedByCode) return ; isTitleCheckBoxSelected.put(position,!isTitleCheckBoxSelected.get(position)); } }); 在onCheckedChanged方法里将对应position的checkbox的状态反转。 细节3):将map中的对应position的状态值赋值给当前的checkbox 但是有个问题,checkbox的setChecked方法,看其源码,会走OnCheckedChangeListener的回调 而此时,setChecked方法我只想设置View的状态,并不想走它的回调方法。下面有2种方法可以解决这个问题 方法1:在setChecked方法的前后用一个变量夹住,在回调方法里通过这个变量判断回调是不是在代码 里通过setChecked触发,如果是setChecked触发的,则不执行map的取反的操作。 1 2 3 isCheckedByCode= true ; header_checkbox.setChecked(isTitleCheckBoxSelected.get(position)); isCheckedByCode= false ; 这种方法多申请了个变量,耦合度比较高。 方法2:在setChecked方法之前将checkbox的监听设置为null,在setChecked方法之后设置真正的监听。 除了checkbox,其它的一些view,也可以通过以上的方法来解决复用的问题。解决复用要遵循一个原则:MV分离,在view一些事件监听里,一般情况下改变记录状态的Map值之后,切记立马就将值设置给View,而应该通过notifyDatasetChanged()方法将状态更新到view上。 本文转自屠夫章哥 51CTO博客,原文链接:http://blog.51cto.com/4259297/1846668,如需转载请自行联系原作者

优秀的个人博客,低调大师

解析:用SAAJ解决SOA集成问题

面向服务的架构(SOA)和面向服务的集成(SOI)得到了广泛的应用,并且有一种需求变得越来越明显:业务需要集成系统,并允许消费者利用基于标准的方法访问服务。SOA是一种架构范例。在这种架构中,可重用的业务逻辑表现为包含基于标准的、定义良好的接口的服务集,并且可以以松散耦合方式访问它。因为Web Service标准的供应商的支持,以及利用基于这种标准的软件集成不同的系统相对容易,所以在解决企业集成问题方面,SOA得到了普遍的认可。 基于Java的中间件和企业应用程序集成(EAI)产品已经被使用了一段时间,现在,许多公司在以不同的方式使用这项技术。虽然一些服务是在Java平台上开发和提供的,但来自基于Java的应用程序的服务消费还有其他灵活的需求。SOAP with Attachments API for Java (SAAJ)通过提供访问服务的标准API来帮助这些服务的消费。 有几个基于Java的API可以用来访问服务。这些服务包括SAAJ、基于Java API for XML 的RPC(JAX-RPC)和Web服务调用框架(WSIF)。SAAJ提供了构建服务消费者以及提供者的API。让我们看一看如何利用SAAJ在SOA中开发Web Service的消费者。我们将举例说明WebLogic Integration的组合过程,该过程有机地结合了获取城市邮政编码的服务、获取邮政分区的温度的服务,以及消费该服务的一个示例SAAJ客户机。 什么是SAAJ? SAAJ是在松散耦合软件系统中利用SOAP协议实现的基于XML消息传递的API规范。顾名思义,SAAJ支持带附件的SOAP消息。 对于Java API for XML Messaging (JAXM),您已经了解很多,并且可能感到奇怪,究竟JAXM发生了什么事?JAXM 1.0的理念是通过提供消息传递和SOAP API,允许开发人员根据SOAP编写支持消息传递标准的业务应用程序。随着JAXM 1.1版的推出,SOAP API (javax.xml.soap)被分割成了SAAJ1.1规范和JAXM1.1,JAXM1.1只包含基于消息传递的API(javax.xml.messaging)。目前,正在使用的SAAJ版本是1.2。WebLogic Server 8.1 SP2 支持SAAJ 1.1规范。 SAAJ 1.2 API主要由javax.xml.soap包组成,它为带有多用途互连网邮件扩展协议(MIME)附件的SOAP消息提供抽象。该API提供了创建到端点的点到点连接的方法、创建并处理SOAP消息和附件的方法,以及接收和处理SOAP错误的方法。 虽然在开发企业应用程序的时候,有几种技术供您选择,但对于不同的问题,某些技术可能更合适。选择正确的工具非常重要。 选择SAAJ的理由是什么呢?SAAJ无疑很适合基于文档的同步或者异步Web Service。SAAJ使用简单,有助于您在Java环境中集成各种Web Service,它扩展了对文档风格的Web Service通信的自然支持(natural support)。SAAJ还支持基于标准接口上的XML消息传递,并且这一点得到了供应商的广泛支持。 编写客户机 清单 1显示了一个用SAAJ编写的简单的消费者,它访问了一个同步WebLogic Integration过程,来获得给定城市的温度。完整的项目可以从 [url]www.WebLogicPro.com[/url]下载。下面将讨论完成该项目所必需的步骤。 面向服务的架构(SOA)和面向服务的集成(SOI)得到了广泛的应用,并且有一种需求变得越来越明显:业务需要集成系统,并允许消费者利用基于标准的方法访问服务。SOA是一种架构范例。在这种架构中,可重用的业务逻辑表现为包含基于标准的、定义良好的接口的服务集,并且可以以松散耦合方式访问它。因为Web Service标准的供应商的支持,以及利用基于这种标准的软件集成不同的系统相对容易,所以在解决企业集成问题方面,SOA得到了普遍的认可。 基于Java的中间件和企业应用程序集成(EAI)产品已经被使用了一段时间,现在,许多公司在以不同的方式使用这项技术。虽然一些服务是在Java平台上开发和提供的,但来自基于Java的应用程序的服务消费还有其他灵活的需求。SOAP with Attachments API for Java (SAAJ)通过提供访问服务的标准API来帮助这些服务的消费。 有几个基于Java的API可以用来访问服务。这些服务包括SAAJ、基于Java API for XML 的RPC(JAX-RPC)和Web服务调用框架(WSIF)。SAAJ提供了构建服务消费者以及提供者的API。让我们看一看如何利用SAAJ在SOA中开发Web Service的消费者。我们将举例说明WebLogic Integration的组合过程,该过程有机地结合了获取城市邮政编码的服务、获取邮政分区的温度的服务,以及消费该服务的一个示例SAAJ客户机。 什么是SAAJ? SAAJ是在松散耦合软件系统中利用SOAP协议实现的基于XML消息传递的API规范。顾名思义,SAAJ支持带附件的SOAP消息。 对于Java API for XML Messaging (JAXM),您已经了解很多,并且可能感到奇怪,究竟JAXM发生了什么事?JAXM 1.0的理念是通过提供消息传递和SOAP API,允许开发人员根据SOAP编写支持消息传递标准的业务应用程序。随着JAXM 1.1版的推出,SOAP API (javax.xml.soap)被分割成了SAAJ1.1规范和JAXM1.1,JAXM1.1只包含基于消息传递的API(javax.xml.messaging)。目前,正在使用的SAAJ版本是1.2。WebLogic Server 8.1 SP2 支持SAAJ 1.1规范。 SAAJ 1.2 API主要由javax.xml.soap包组成,它为带有多用途互连网邮件扩展协议(MIME)附件的SOAP消息提供抽象。该API提供了创建到端点的点到点连接的方法、创建并处理SOAP消息和附件的方法,以及接收和处理SOAP错误的方法。 虽然在开发企业应用程序的时候,有几种技术供您选择,但对于不同的问题,某些技术可能更合适。选择正确的工具非常重要。 选择SAAJ的理由是什么呢?SAAJ无疑很适合基于文档的同步或者异步Web Service。SAAJ使用简单,有助于您在Java环境中集成各种Web Service,它扩展了对文档风格的Web Service通信的自然支持(natural support)。SAAJ还支持基于标准接口上的XML消息传递,并且这一点得到了供应商的广泛支持。 编写客户机 清单 1显示了一个用SAAJ编写的简单的消费者,它访问了一个同步WebLogic Integration过程,来获得给定城市的温度。完整的项目可以从 [url]www.WebLogicPro.com[/url]下载。下面将讨论完成该项目所必需的步骤。 面向服务的架构(SOA)和面向服务的集成(SOI)得到了广泛的应用,并且有一种需求变得越来越明显:业务需要集成系统,并允许消费者利用基于标准的方法访问服务。SOA是一种架构范例。在这种架构中,可重用的业务逻辑表现为包含基于标准的、定义良好的接口的服务集,并且可以以松散耦合方式访问它。因为Web Service标准的供应商的支持,以及利用基于这种标准的软件集成不同的系统相对容易,所以在解决企业集成问题方面,SOA得到了普遍的认可。 基于Java的中间件和企业应用程序集成(EAI)产品已经被使用了一段时间,现在,许多公司在以不同的方式使用这项技术。虽然一些服务是在Java平台上开发和提供的,但来自基于Java的应用程序的服务消费还有其他灵活的需求。SOAP with Attachments API for Java (SAAJ)通过提供访问服务的标准API来帮助这些服务的消费。 有几个基于Java的API可以用来访问服务。这些服务包括SAAJ、基于Java API for XML 的RPC(JAX-RPC)和Web服务调用框架(WSIF)。SAAJ提供了构建服务消费者以及提供者的API。让我们看一看如何利用SAAJ在SOA中开发Web Service的消费者。我们将举例说明WebLogic Integration的组合过程,该过程有机地结合了获取城市邮政编码的服务、获取邮政分区的温度的服务,以及消费该服务的一个示例SAAJ客户机。 什么是SAAJ? SAAJ是在松散耦合软件系统中利用SOAP协议实现的基于XML消息传递的API规范。顾名思义,SAAJ支持带附件的SOAP消息。 对于Java API for XML Messaging (JAXM),您已经了解很多,并且可能感到奇怪,究竟JAXM发生了什么事?JAXM 1.0的理念是通过提供消息传递和SOAP API,允许开发人员根据SOAP编写支持消息传递标准的业务应用程序。随着JAXM 1.1版的推出,SOAP API (javax.xml.soap)被分割成了SAAJ1.1规范和JAXM1.1,JAXM1.1只包含基于消息传递的API(javax.xml.messaging)。目前,正在使用的SAAJ版本是1.2。WebLogic Server 8.1 SP2 支持SAAJ 1.1规范。 SAAJ 1.2 API主要由javax.xml.soap包组成,它为带有多用途互连网邮件扩展协议(MIME)附件的SOAP消息提供抽象。该API提供了创建到端点的点到点连接的方法、创建并处理SOAP消息和附件的方法,以及接收和处理SOAP错误的方法。 虽然在开发企业应用程序的时候,有几种技术供您选择,但对于不同的问题,某些技术可能更合适。选择正确的工具非常重要。 选择SAAJ的理由是什么呢?SAAJ无疑很适合基于文档的同步或者异步Web Service。SAAJ使用简单,有助于您在Java环境中集成各种Web Service,它扩展了对文档风格的Web Service通信的自然支持(natural support)。SAAJ还支持基于标准接口上的XML消息传递,并且这一点得到了供应商的广泛支持。 编写客户机 清单 1显示了一个用SAAJ编写的简单的消费者,它访问了一个同步WebLogic Integration过程,来获得给定城市的温度。完整的项目可以从 [url]www.WebLogicPro.com[/url]下载。下面将讨论完成该项目所必需的步骤。 面向服务的架构(SOA)和面向服务的集成(SOI)得到了广泛的应用,并且有一种需求变得越来越明显:业务需要集成系统,并允许消费者利用基于标准的方法访问服务。SOA是一种架构范例。在这种架构中,可重用的业务逻辑表现为包含基于标准的、定义良好的接口的服务集,并且可以以松散耦合方式访问它。因为Web Service标准的供应商的支持,以及利用基于这种标准的软件集成不同的系统相对容易,所以在解决企业集成问题方面,SOA得到了普遍的认可。 基于Java的中间件和企业应用程序集成(EAI)产品已经被使用了一段时间,现在,许多公司在以不同的方式使用这项技术。虽然一些服务是在Java平台上开发和提供的,但来自基于Java的应用程序的服务消费还有其他灵活的需求。SOAP with Attachments API for Java (SAAJ)通过提供访问服务的标准API来帮助这些服务的消费。 有几个基于Java的API可以用来访问服务。这些服务包括SAAJ、基于Java API for XML 的RPC(JAX-RPC)和Web服务调用框架(WSIF)。SAAJ提供了构建服务消费者以及提供者的API。让我们看一看如何利用SAAJ在SOA中开发Web Service的消费者。我们将举例说明WebLogic Integration的组合过程,该过程有机地结合了获取城市邮政编码的服务、获取邮政分区的温度的服务,以及消费该服务的一个示例SAAJ客户机。 什么是SAAJ? SAAJ是在松散耦合软件系统中利用SOAP协议实现的基于XML消息传递的API规范。顾名思义,SAAJ支持带附件的SOAP消息。 对于Java API for XML Messaging (JAXM),您已经了解很多,并且可能感到奇怪,究竟JAXM发生了什么事?JAXM 1.0的理念是通过提供消息传递和SOAP API,允许开发人员根据SOAP编写支持消息传递标准的业务应用程序。随着JAXM 1.1版的推出,SOAP API (javax.xml.soap)被分割成了SAAJ1.1规范和JAXM1.1,JAXM1.1只包含基于消息传递的API(javax.xml.messaging)。目前,正在使用的SAAJ版本是1.2。WebLogic Server 8.1 SP2 支持SAAJ 1.1规范。 SAAJ 1.2 API主要由javax.xml.soap包组成,它为带有多用途互连网邮件扩展协议(MIME)附件的SOAP消息提供抽象。该API提供了创建到端点的点到点连接的方法、创建并处理SOAP消息和附件的方法,以及接收和处理SOAP错误的方法。 虽然在开发企业应用程序的时候,有几种技术供您选择,但对于不同的问题,某些技术可能更合适。选择正确的工具非常重要。 选择SAAJ的理由是什么呢?SAAJ无疑很适合基于文档的同步或者异步Web Service。SAAJ使用简单,有助于您在Java环境中集成各种Web Service,它扩展了对文档风格的Web Service通信的自然支持(natural support)。SAAJ还支持基于标准接口上的XML消息传递,并且这一点得到了供应商的广泛支持。 编写客户机 清单 1显示了一个用SAAJ编写的简单的消费者,它访问了一个同步WebLogic Integration过程,来获得给定城市的温度。完整的项目可以从 [url]www.WebLogicPro.com[/url]下载。下面将讨论完成该项目所必需的步骤。 清单 2展示了如何利用清单 1中给出的方法调用该服务。这个过程的端点URL是随输入的XML文档字符串一起传递给invokeService()方法的。 清单 2. 以下是利用SAAJ调用WebLogic Integration过程的一个例子。 String inputXml = " "xmlns:soapenc=\"http://schemas.xmlsoap.org/soapencoding/\" xmlns:xsd=\"http://www.w3.org/001/XMLSchema\" " + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"> " + "" + " Barrington IL " + " "; invokeService("http://localhost:7001/saajService/processes/TempService.jpd",inputXml); SAAJ可能是SOA和集成领域中一种非常有用的API。SAAJ的一些应用程序包含测试框架的服务和服务调用的库。我们已经展示了如何利用SAAJ编写消费者,并创建了一个访问WebLogic Integration Get Temperature组合过程的示例客户机。将该示例应用到您自己的项目中,让SAAJ为您工作。 本文转自 牛海彬 51CTO博客,原文链接:http://blog.51cto.com/newhappy/77095,如需转载请自行联系原作者

优秀的个人博客,低调大师

hadoop之 解析HDFS的写文件流程

文件是如何写入HDFS的 ? 下面我们来先看看下面的“写”流程图: 假如我们有一个文件test.txt,想要把它放到Hadoop上,执行如下命令: 引用 # hadoop fs -put /usr/bigdata/dataset/input/20130706/test.txt /opt/bigdata/hadoop/dataset/input/20130706 //或执行下面的命令 # hadoop fs -copyFromLocal /usr/bigdata/dataset/input/20130706/test.txt /opt/bigdata/hadoop/dataset/input/20130706 整个写流程如下: 第一步,客户端调用DistributedFileSystem的create()方法,开始创建新文件:DistributedFileSystem创建DFSOutputStream,产生一个RPC调用,让NameNode在文件系统的命名空间中创建这一新文件; 第二步,NameNode接收到用户的写文件的RPC请求后,谁偶先要执行各种检查,如客户是否有相关的创佳权限和该文件是否已存在等,检查都通过后才会创建一个新文件,并将操作记录到编辑日志,然后DistributedFileSystem会将DFSOutputStream对象包装在FSDataOutStream实例中,返回客户端;否则文件创建失败并且给客户端抛IOException。 第三步,客户端开始写文件:DFSOutputStream会将文件分割成packets数据包,然后将这些packets写到其内部的一个叫做data queue(数据队列)。data queue会向NameNode节点请求适合存储数据副本的DataNode节点的列表,然后这些DataNode之前生成一个Pipeline数据流管道,我们假设副本集参数被设置为3,那么这个数据流管道中就有三个DataNode节点。 第四步,首先DFSOutputStream会将packets向Pipeline数据流管道中的第一个DataNode节点写数据,第一个DataNode接收packets然后把packets写向Pipeline中的第二个节点,同理,第二个节点保存接收到的数据然后将数据写向Pipeline中的第三个DataNode节点。 第五步,DFSOutputStream内部同样维护另外一个内部的写数据确认队列——ack queue。当Pipeline中的第三个DataNode节点将packets成功保存后,该节点回向第二个DataNode返回一个确认数据写成功的信息,第二个DataNode接收到该确认信息后在当前节点数据写成功后也会向Pipeline中第一个DataNode节点发送一个确认数据写成功的信息,然后第一个节点在收到该信息后如果该节点的数据也写成功后,会将packets从ack queue中将数据删除。 在写数据的过程中,如果Pipeline数据流管道中的一个DataNode节点写失败了会发生什问题、需要做哪些内部处理呢?如果这种情况发生,那么就会执行一些操作: 首先,Pipeline数据流管道会被关闭,ack queue中的packets会被添加到data queue的前面以确保不会发生packets数据包的丢失,为存储在另一正常dataname的当前数据指定一个新的标识,并将该标识传送给namenode,以便故障datanode在恢复后可以删除存储的部分数据块; 接着,在正常的DataNode节点上的以保存好的block的ID版本会升级——这样发生故障的DataNode节点上的block数据会在节点恢复正常后被删除,失效节点也会被从Pipeline中删除; 最后,剩下的数据会被写入到Pipeline数据流管道中的其他两个节点中。 如果Pipeline中的多个节点在写数据是发生失败,那么只要写成功的block的数量达到dfs.replication.min(默认为1),那么就任务是写成功的,然后NameNode后通过一步的方式将block复制到其他节点,最后事数据副本达到dfs.replication参数配置的个数。 第六步,,完成写操作后,客户端调用close()关闭写操作,刷新数据; 第七步,,在数据刷新完后NameNode后关闭写操作流。到此,整个写操作完成。 文章可以转载,必须以链接形式标明出处。 本文转自 张冲andy 博客园博客,原文链接: http://www.cnblogs.com/andy6/p/7490307.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android ApiDemo 系列解析【View->Button】

一直想把 Android 的 APIDEMO 里面的DEMO 写出来,一来可以方便自己查看,二来方便大家能够早一点切入,从基础的控件到界面布局乃至到OPEN GL 等深入知识。不过惰性使然,一直迟迟未动手,今天外面下着大雨,没心思出去,听说外面还有人拍到深圳的“双龙戏水”呵呵,跑题了。 好了,从头开始吧,看了一下 APIDEMO 觉得先从Button 讲起然后逐步深入,对于新手或者初学的童鞋帮助会大一点,那么我们开始吧,讲解大纲。 ApiDemos 中运行的效果 1、本篇讲述点在 ApiDemos 位置。 2、为Button 添加系统样式。 3、额外扩展。 1、本篇讲述点在 ApiDemos 位置 src文件 com.example.android.apis.view 下的 Buttons1.java文件 源码为: ApiDemos src 代码 package com.example.android.apis.view; import com.example.android.apis.R; import android.app.Activity; import android.os.Bundle; public class Buttons1 extends Activity{ @Override protected void onCreate(BundlesavedInstanceState){ super .onCreate(savedInstanceState); setContentView(R.layout.buttons_1); } } res 文件 layout 下的 buttons_1.xml 文件, 源码为: <? xmlversion="1.0"encoding="utf-8" ?> < ScrollView xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="fill_parent" android:layout_height ="fill_parent" > < LinearLayout android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:orientation ="vertical" > <!-- Regularsizedbuttons --> < Button android:id ="@+id/button_normal" android:text ="@string/buttons_1_normal" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> <!-- Smallbuttons --> < Button android:id ="@+id/button_small" style ="?android:attr/buttonStyleSmall" android:text ="@string/buttons_1_small" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> < ToggleButton android:id ="@+id/button_toggle" android:text ="@string/buttons_1_toggle" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> </ LinearLayout > </ ScrollView > 2、为Button 添加系统样式 从 ApiDemos 中看到第二个Button 为一个 style 的样式属性,此属性为通过调用系统的内置样式文件设置 Button 的大小。 格式为:“?[package:][type:]name” ,Constant Value 为 16842825 (0x01010049)。docs 文件解释为: 上面的XML中,定义了三个 Button 。依次往下看,第一个为普通 Button ,第二个是一个加了 Style 的 Button ,第三个为 ToggleButton 看名字可以看出来,是一个做开关作用的按钮,作用为在两种状态中来回切换。 3、额外扩展 由于 ApiDemos 中对 Button 的介绍较少,在这里我将加一些内容进来,以便开始学的朋友能够较早的使用 Button 做一些你想做的事情,当然我所说的就是交互。内容如下: 1、如何在 布局中找到 View 即你要的按钮 2、为 Button 添加事件监听 3、为 Button 添加按上按下切换图片效果 4、为 ToggleButton 添加状态改变事件监听 1、如何在 布局中找到 View 即你要的按钮 代码如下: void findView(){ normal = (Button)findViewById(R.id.button_normal); small = (Button)findViewById(R.id.button_small); btn = (ToggleButton)findViewById(R.id.button_toggle); } Tip: 所查找的 VIEW 必须 是当前setConteView 方法的布局文件下的VIEW ,如果查找的另外一个XML布局的VIEW 将会报空指针。这点对于新手来说要记住,如果你要在当前布局中查找另外XML的VIEW 将会在后续的文章一一揭晓。 2、为 Button 添加事件监听代码如下: void setOnclick(Buttonbtn){ btn.setOnClickListener( new OnClickListener(){ public void onClick(Viewv){ // TODOAuto-generatedmethodstub switch (v.getId()){ case R.id.button_normal: setTitle( " 点击了普通按钮 " ); break ; case R.id.button_small: setTitle( " 点击小按钮 " ); break ; default : break ; } } }); } Tip:在这里用了一个方法,通过点击的view 来判断我点击是哪个按钮,当然你也可以直接获取button 然后通过button的 一个监听也可以,写法有很多种。 为 Button 添加按上按下切换图片效果代码如下: void setOnTouch( final Buttonbtn){ btn.setOnTouchListener( new OnTouchListener(){ public boolean onTouch(Viewv,MotionEventevent){ // TODOAuto-generatedmethodstub switch (event.getAction()){ case MotionEvent.ACTION_UP: btn.setBackgroundResource(R.drawable.add); break ; case MotionEvent.ACTION_DOWN: btn.setBackgroundResource(R.drawable.add_user); break ; default : break ; } return false ; } }); } Tip:记住在 Android 中你如果想要为 诸如 LinearLayout 等布局文件设置onTouch事件貌似不会激发MotionEvent.ACTION_UP 事件,在Button 和ImageButton是可以实现的,此功能可以为按钮做按上换图片换下换图片的交互效果。 为 ToggleButton 添加状态改变事件监听代码如下: btn.setOnCheckedChangeListener( new OnCheckedChangeListener(){ public void onCheckedChanged(CompoundButtonbuttonView, boolean isChecked){ // TODOAuto-generatedmethodstub if (isChecked){ setTitle( " 选中 " ); } else { setTitle( " 反选 " ); } } }); Ok,第一篇到此为止,本篇为开头篇希望多多提意见。 运行效果: 源码下载: /Files/TerryBlog/Button.rar 本文转自 terry_龙 51CTO博客,原文链接:http://blog.51cto.com/terryblog/362178,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android 属性动画(Property Animation) 完全解析 (上)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38067475 1、概述 Android提 供了几种动画类型:View Animation 、Drawable Animation 、Property Animation 。View Animation相当简单,不过只能支持简单的缩放、平移、旋转、透明度基本的动画,且有一定的局限性。比如:你希望View有一个颜色的切换动画;你 希望可以使用3D旋转动画;你希望当动画停止时,View的位置就是当前的位置;这些View Animation都无法做到。这就是Property Animation产生的原因,本篇博客详细介绍Property Animation的用法。至于Drawable Animation,嗯,略~ 2、相关API Property Animation故名思议就是通过动画的方式改变对象的属性了,我们首先需要了解几个属性: Duration动画的持续时间,默认300ms。 Time interpolation:时间差值,乍一看不知道是什么,但是我说LinearInterpolator、AccelerateDecelerateInterpolator,大家一定知道是干嘛的了,定义动画的变化率。 Repeat count and behavior:重复次数、以及重复模式;可以定义重复多少次;重复时从头开始,还是反向。 Animator sets: 动画集合,你可以定义一组动画,一起执行或者顺序执行。 Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管。 相关的类 ObjectAnimator 动画的执行类,后面详细介绍 ValueAnimator 动画的执行类,后面详细介绍 AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。 AnimatorInflater 用户加载属性动画的xml文件 TypeEvaluator 类型估值,主要用于设置动画操作属性的值。 TimeInterpolator 时间插值,上面已经介绍。 总的来说,属性动画就是,动画的执行类来设置动画操作的对象的属性、持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态的变化对象的属性。 3、ObjectAnimator实现动画 之所以选择ObjectAnimator为第一个~~是因为,这个实现最简单~~一行代码,秒秒钟实现动画,下面看个例子: 布局文件: [html] view plain copy <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/id_container"> <ImageView android:id="@+id/id_ball" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/mv" android:scaleType="centerCrop" android:onClick="rotateyAnimRun" /> </RelativeLayout> 很简单,就一张妹子图片~ Activity代码: [java] view plain copy packagecom.example.zhy_property_animation; importandroid.animation.ObjectAnimator; importandroid.app.Activity; importandroid.os.Bundle; importandroid.view.View; publicclassObjectAnimActivityextendsActivity { @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.xml_for_anim); } publicvoidrotateyAnimRun(Viewview) { ObjectAnimator// .ofFloat(view,"rotationX",0.0F,360.0F)// .setDuration(500)// .start(); } } 效果: 是不是一行代码就能实现简单的动画~~ 对于ObjectAnimator 1、提供了ofInt、ofFloat、ofObject,这几个方法都是设置动画作用的元素、作用的属性、动画开始、结束、以及中间的任意个属性值。 当对于属性值,只设置一个的时候,会认为当然对象该属性的值为开始(getPropName反射获取),然后设置的值为终点。如果设置两个,则一个为开始、一个为结束~~~ 动画更新的过程中,会不断调用setPropName更新元素的属性,所有使用ObjectAnimator更新某个属性,必须得有getter(设置一个属性值的时候)和setter方法~ 2、如果你操作对象的该属性方法里面,比如上例的setRotationX如果内部没有调用view的重绘,则你需要自己按照下面方式手动调用。 [java] view plain copy anim.addUpdateListener(newAnimatorUpdateListener() { @Override publicvoidonAnimationUpdate(ValueAnimatoranimation) { //view.postInvalidate(); //view.invalidate(); } }); 3、看了上面的例子,因为设置的操作的属性只有一个,那么如果我希望一个动画能够让View既可以缩小、又能够淡出(3个属性scaleX,scaleY,alpha),只使用ObjectAnimator咋弄? 想法是不是很不错,可能会说使用AnimatorSet啊,这一看就是一堆动画塞一起执行,但是我偏偏要用一个ObjectAnimator实例实现呢~下面看代码: [java] view plain copy publicvoidrotateyAnimRun(finalViewview) { ObjectAnimatoranim=ObjectAnimator// .ofFloat(view,"zhy",1.0F,0.0F)// .setDuration(500);// anim.start(); anim.addUpdateListener(newAnimatorUpdateListener() { @Override publicvoidonAnimationUpdate(ValueAnimatoranimation) { floatcVal=(Float)animation.getAnimatedValue(); view.setAlpha(cVal); view.setScaleX(cVal); view.setScaleY(cVal); } }); } 把设置属性的那个字符串,随便写一个该对象没有的属性,就是不管~~咱们只需要它按照时间插值和持续时间计算的那个值,我们自己手动调用~ 效果: 这个例子就是想说明一下,有时候换个思路不要被API所约束,利用部分API提供的功能也能实现好玩的效果~~~ 比如:你想实现抛物线的效果,水平方向100px/s,垂直方向加速度200px/s*s ,咋实现呢~~可以自己用ObjectAnimator试试~ 4、其实还有更简单的方式,实现一个动画更改多个效果:使用propertyValuesHolder [java] view plain copy publicvoidpropertyValuesHolder(Viewview) { PropertyValuesHolderpvhX=PropertyValuesHolder.ofFloat("alpha",1f, 0f,1f); PropertyValuesHolderpvhY=PropertyValuesHolder.ofFloat("scaleX",1f, 0,1f); PropertyValuesHolderpvhZ=PropertyValuesHolder.ofFloat("scaleY",1f, 0,1f); ObjectAnimator.ofPropertyValuesHolder(view,pvhX,pvhY,pvhZ).setDuration(1000).start(); } 4、ValueAnimator实现动画 和ObjectAnimator用法很类似,简单看一下用view垂直移动的动画代码: [java] view plain copy publicvoidverticalRun(Viewview) { ValueAnimatoranimator=ValueAnimator.ofFloat(0,mScreenHeight -mBlueBall.getHeight()); animator.setTarget(mBlueBall); animator.setDuration(1000).start(); } 给你的感觉是不是,坑爹啊,这和ValueAnimator有毛线区别~但是仔细看,你看会发现,没有设置操作的属性~~也就是说,上述代码是没有任何效果的,没有指定属性~ 这就是和ValueAnimator的区别之处:ValueAnimator并没有在属性上做操作,你可能会问这样有啥好处?我岂不是还得手动设置? 好 处:不需要操作的对象的属性一定要有getter和setter方法,你可以自己根据当前动画的计算值,来操作任何属性,记得上例的那个【我希望一个动画 能够让View既可以缩小、又能够淡出(3个属性scaleX,scaleY,alpha)】吗?其实就是这么个用法~ 实例: 布局文件: [html] view plain copy <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/id_container" > <ImageView android:id="@+id/id_ball" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/bol_blue"/> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="verticalRun" android:text="垂直"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="paowuxian" android:text="抛物线"/> </LinearLayout> </RelativeLayout> 左上角一个小球,底部两个按钮~我们先看一个自由落体的代码: [java] view plain copy /** *自由落体 *@paramview */ publicvoidverticalRun(Viewview) { ValueAnimatoranimator=ValueAnimator.ofFloat(0,mScreenHeight -mBlueBall.getHeight()); animator.setTarget(mBlueBall); animator.setDuration(1000).start(); //animator.setInterpolator(value) animator.addUpdateListener(newAnimatorUpdateListener() { @Override publicvoidonAnimationUpdate(ValueAnimatoranimation) { mBlueBall.setTranslationY((Float)animation.getAnimatedValue()); } }); } 与ObjectAnimator不同的就是我们自己设置元素属性的更新~虽然多了几行代码,但是貌似提高灵活性~ 下 面再来一个例子,如果我希望小球抛物线运动【实现抛物线的效果,水平方向100px/s,垂直方向加速度200px/s*s】,分析一下,貌似只和时间 有关系,但是根据时间的变化,横向和纵向的移动速率是不同的,我们该咋实现呢?此时就要重写TypeValue的时候了,因为我们在时间变化的同时,需要 返回给对象两个值,x当前位置,y当前位置: 代码: [java] view plain copy /** *抛物线 *@paramview */ publicvoidpaowuxian(Viewview) { ValueAnimatorvalueAnimator=newValueAnimator(); valueAnimator.setDuration(3000); valueAnimator.setObjectValues(newPointF(0,0)); valueAnimator.setInterpolator(newLinearInterpolator()); valueAnimator.setEvaluator(newTypeEvaluator<PointF>() { //fraction=t/duration @Override publicPointFevaluate(floatfraction,PointFstartValue, PointFendValue) { Log.e(TAG,fraction*3+""); //x方向200px/s,则y方向0.5*10*t PointFpoint=newPointF(); point.x=200*fraction*3; point.y=0.5f*200*(fraction*3)*(fraction*3); returnpoint; } }); valueAnimator.start(); valueAnimator.addUpdateListener(newAnimatorUpdateListener() { @Override publicvoidonAnimationUpdate(ValueAnimatoranimation) { PointFpoint=(PointF)animation.getAnimatedValue(); mBlueBall.setX(point.x); mBlueBall.setY(point.y); } }); } 可 以看到,因为ofInt,ofFloat等无法使用,我们自定义了一个TypeValue,每次根据当前时间返回一个PointF对象,(PointF和 Point的区别就是x,y的单位一个是float,一个是int;RectF,Rect也是)PointF中包含了x,y的当前位置~然后我们在监听器 中获取,动态设置属性: 效果图: 有木有两个铁球同时落地的感觉~~对,我应该搞两个球~~ps:物理公式要是错了,就当没看见哈 自定义TypeEvaluator传入的泛型可以根据自己的需求,自己设计个Bean。 好 了,我们已经分别讲解了ValueAnimator和ObjectAnimator实现动画;二者区别;如何利用部分API,自己更新属性实现效果;自定 义TypeEvaluator实现我们的需求;但是我们并没有讲如何设计插值,其实我觉得把,这个插值默认的那一串实现类够用了~~很少,会自己去设计个 超级变态的~嗯~所以:略。 5、监听动画的事件 对于动画,一般都是一些辅助效果,比如我要删除个元素,我可能希望是个淡出的效果,但是最终还是要删掉,并不是你透明度没有了,还占着位置,所以我们需要知道动画如何结束。 所以我们可以添加一个动画的监听: [java] view plain copy publicvoidfadeOut(Viewview) { ObjectAnimatoranim=ObjectAnimator.ofFloat(mBlueBall,"alpha",0.5f); anim.addListener(newAnimatorListener() { @Override publicvoidonAnimationStart(Animatoranimation) { Log.e(TAG,"onAnimationStart"); } @Override publicvoidonAnimationRepeat(Animatoranimation) { //TODOAuto-generatedmethodstub Log.e(TAG,"onAnimationRepeat"); } @Override publicvoidonAnimationEnd(Animatoranimation) { Log.e(TAG,"onAnimationEnd"); ViewGroupparent=(ViewGroup)mBlueBall.getParent(); if(parent!=null) parent.removeView(mBlueBall); } @Override publicvoidonAnimationCancel(Animatoranimation) { //TODOAuto-generatedmethodstub Log.e(TAG,"onAnimationCancel"); } }); anim.start(); } 这样就可以监听动画的开始、结束、被取消、重复等事件~但是有时候会觉得,我只要知道结束就行了,这么长的代码我不能接收,那你可以使用AnimatorListenerAdapter [java] view plain copy anim.addListener(newAnimatorListenerAdapter() { @Override publicvoidonAnimationEnd(Animatoranimation) { Log.e(TAG,"onAnimationEnd"); ViewGroupparent=(ViewGroup)mBlueBall.getParent(); if(parent!=null) parent.removeView(mBlueBall); } }); AnimatorListenerAdapter继承了AnimatorListener接口,然后空实现了所有的方法~ 效果图: animator还有cancel()和end()方法:cancel动画立即停止,停在当前的位置;end动画直接到最终状态。 6、AnimatorSet的使用 实例: 布局文件: [html] view plain copy <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/id_container" > <ImageView android:id="@+id/id_ball" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/bol_blue"/> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:orientation="horizontal"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="togetherRun" android:text="简单的多动画Together"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="playWithAfter" android:text="多动画按次序执行"/> </LinearLayout> </RelativeLayout> 继续玩球~ 代码: [java] view plain copy packagecom.example.zhy_property_animation; importandroid.animation.AnimatorSet; importandroid.animation.ObjectAnimator; importandroid.app.Activity; importandroid.os.Bundle; importandroid.view.View; importandroid.view.animation.LinearInterpolator; importandroid.widget.ImageView; publicclassAnimatorSetActivityextendsActivity { privateImageViewmBlueBall; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.anim_set); mBlueBall=(ImageView)findViewById(R.id.id_ball); } publicvoidtogetherRun(Viewview) { ObjectAnimatoranim1=ObjectAnimator.ofFloat(mBlueBall,"scaleX", 1.0f,2f); ObjectAnimatoranim2=ObjectAnimator.ofFloat(mBlueBall,"scaleY", 1.0f,2f); AnimatorSetanimSet=newAnimatorSet(); animSet.setDuration(2000); animSet.setInterpolator(newLinearInterpolator()); //两个动画同时执行 animSet.playTogether(anim1,anim2); animSet.start(); } publicvoidplayWithAfter(Viewview) { floatcx=mBlueBall.getX(); ObjectAnimatoranim1=ObjectAnimator.ofFloat(mBlueBall,"scaleX", 1.0f,2f); ObjectAnimatoranim2=ObjectAnimator.ofFloat(mBlueBall,"scaleY", 1.0f,2f); ObjectAnimatoranim3=ObjectAnimator.ofFloat(mBlueBall, "x",cx,0f); ObjectAnimatoranim4=ObjectAnimator.ofFloat(mBlueBall, "x",cx); /** *anim1,anim2,anim3同时执行 *anim4接着执行 */ AnimatorSetanimSet=newAnimatorSet(); animSet.play(anim1).with(anim2); animSet.play(anim2).with(anim3); animSet.play(anim4).after(anim3); animSet.setDuration(1000); animSet.start(); } } 写了两个效果: 第一:使用playTogether两个动画同时执行,当然还有playSequentially依次执行~~ 第二:如果我们有一堆动画,如何使用代码控制顺序,比如1,2同时;3在2后面;4在1之前等~就是效果2了 有一点注意:animSet.play().with();也是支持链式编程的,但是不要想着狂点,比如 animSet.play(anim1).with(anim2).before(anim3).before(anim5); 这样是不行的,系统不会根据你写的这一长串来决定先后的顺序,所以麻烦你按照上面例子的写法,多写几行: 效果图: 好了,由于篇幅~~关于属性动画还有点知识: 1、xml文件创建属性动画 2、布局动画 3、View的animate方法等。 那就考虑写到下一篇了,不过核心的功能就这些了~~ 对了,如果使用11以下的SDK ,请导入nineoldandroids动画库,用法基本完全一致~ 源码点击下载 本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/5534210.html,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册