Android学习分享:执行某ViewGroup的动画时,子控件太多导致动画执行卡顿的问题

最近在项目中遇到一个问题,我有一个LinearLayout,里面装载了许多ImageView控件,ImageView控件显示着自己的图片,这个LinearLayout支持双指缩放,缩放采用ScaleAnimation来实现,但是但是在缩放过程中,屏幕十分卡顿,缩放效果根本没有跟上手指的缩放动作。后来在Google上查了一番,查到一个API,叫setAnimationDrawCacheEnabled(boolean enabled):

    /**
     * Enables or disables the children‘s drawing cache during a layout animation.
     * By default, the drawing cache is enabled but this will prevent nested
     * layout animations from working. To nest animations, you must disable the
     * cache.
     *
     * @param enabled true to enable the animation cache, false otherwise
     *
     * @see #isAnimationCacheEnabled()
     * @see View#setDrawingCacheEnabled(boolean)
     */
    public void setAnimationCacheEnabled(boolean enabled) {
        setBooleanFlag(FLAG_ANIMATION_CACHE, enabled);
    }

方法的注解我这里简单翻译一下:在执行一个Layout动画时开启或关闭子控件的绘制缓存。默认情况下,绘制缓存是开启的,但是这将阻止嵌套Layout动画的正常执行。对于嵌套动画,你必须禁用这个缓存。

先说drawing cache,绘制缓存的概念,Android为了提高View视图的绘制效率,提出了一个缓存的概念,其实就是一个Bitmap,用来存储View当前的绘制内容,在View的内容或者尺寸未发生改变时,这个缓存应该始终不被销毁,销毁了如果下次还用(开启了绘图缓存的前提下,API为setDrawingCacheEnabled(enabled),另外还可以设置绘图缓存Bitmap的质量,API为setDrawingCacheQuality(quality))就必须重建。

关于绘图缓存的相关介绍,可搜索这些相关API的介绍:

1)setDrawingCacheQuality(int quality)

2)setDrawingCacheEnabled(enabled)

3)setDrawingCacheBackgroundColor(color)

先看一段代码:

    /**
     * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>
     *
     * <p>If you call {@link #buildDrawingCache()} manually without calling
     * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
     * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p>
     *
     * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
     * this method will create a bitmap of the same size as this view. Because this bitmap
     * will be drawn scaled by the parent ViewGroup, the result on screen might show
     * scaling artifacts. To avoid such artifacts, you should call this method by setting
     * the auto scaling to true. Doing so, however, will generate a bitmap of a different
     * size than the view. This implies that your application must be able to handle this
     * size.</p>
     *
     * <p>You should avoid calling this method when hardware acceleration is enabled. If
     * you do not need the drawing cache bitmap, calling this method will increase memory
     * usage and cause the view to be rendered in software once, thus negatively impacting
     * performance.</p>
     *
     * @see #getDrawingCache()
     * @see #destroyDrawingCache()
     */
    public void buildDrawingCache(boolean autoScale) {
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                mDrawingCache == null : mUnscaledDrawingCache == null)) {
            mCachingFailed = false;

            int width = mRight - mLeft;
            int height = mBottom - mTop;

            final AttachInfo attachInfo = mAttachInfo;
            final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;

            if (autoScale && scalingRequired) {
                width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
                height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
            }

            final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;       // 1.这里
            final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
            final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;

            final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
            final long drawingCacheSize =
                    ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
            if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
                if (width > 0 && height > 0) {
                    Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs "
                            + projectedBitmapSize + " bytes, only "
                            + drawingCacheSize + " available");
                }
                destroyDrawingCache();
                mCachingFailed = true;
                return;
            }

            boolean clear = true;
            Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;

            if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
                Bitmap.Config quality;          // 2.这里
                if (!opaque) {
                    // Never pick ARGB_4444 because it looks awful
                    // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
                    switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
                        case DRAWING_CACHE_QUALITY_AUTO:
                        case DRAWING_CACHE_QUALITY_LOW:
                        case DRAWING_CACHE_QUALITY_HIGH:
                        default:
                            quality = Bitmap.Config.ARGB_8888;
                            break;
                    }
                } else {
                    // Optimization for translucent windows
                    // If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
                    quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
                }

                // Try to cleanup memory
                if (bitmap != null) bitmap.recycle();

                try {
                    bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
                            width, height, quality);
                    bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
                    if (autoScale) {
                        mDrawingCache = bitmap;
                    } else {
                        mUnscaledDrawingCache = bitmap;
                    }
                    if (opaque && use32BitCache) bitmap.setHasAlpha(false);
                } catch (OutOfMemoryError e) {
                    // If there is not enough memory to create the bitmap cache, just
                    // ignore the issue as bitmap caches are not required to draw the
                    // view hierarchy
                    if (autoScale) {
                        mDrawingCache = null;
                    } else {
                        mUnscaledDrawingCache = null;
                    }
                    mCachingFailed = true;
                    return;
                }

                clear = drawingCacheBackgroundColor != 0;
            }

            Canvas canvas;
            if (attachInfo != null) {
                canvas = attachInfo.mCanvas;
                if (canvas == null) {
                    canvas = new Canvas();
                }
                canvas.setBitmap(bitmap);
                // Temporarily clobber the cached Canvas in case one of our children
                // is also using a drawing cache. Without this, the children would
                // steal the canvas by attaching their own bitmap to it and bad, bad
                // thing would happen (invisible views, corrupted drawings, etc.)
                attachInfo.mCanvas = null;
            } else {
                // This case should hopefully never or seldom happen
                canvas = new Canvas(bitmap);
            }

            if (clear) {
                bitmap.eraseColor(drawingCacheBackgroundColor);
            }

            computeScroll();
            final int restoreCount = canvas.save();

            if (autoScale && scalingRequired) {
                final float scale = attachInfo.mApplicationScale;
                canvas.scale(scale, scale);
            }

            canvas.translate(-mScrollX, -mScrollY);

            mPrivateFlags |= PFLAG_DRAWN;
            if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
                    mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
            }

            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
                if (mOverlay != null && !mOverlay.isEmpty()) {
                    mOverlay.getOverlayView().draw(canvas);
                }
            } else {
                draw(canvas);
            }

            canvas.restoreToCount(restoreCount);
            canvas.setBitmap(null);

            if (attachInfo != null) {
                // Restore the cached Canvas for our siblings
                attachInfo.mCanvas = canvas;
            }
        }
    }

两个标注了红色的地方,说明了这个mDrawingCacheBackgroundColor变量的作用,因此如果使用默认值,那么缓存Bitmap使用的是Bitmap.Config.ARGB_8888,比Bitmap.Config.RGB_565多占用了一半的内存,因此如果不想使用太大的内存,担心内存泄露,可以设置给mDrawingCacheBackgroundColor一个值,例如:

setDrawingCacheBackgroundColor(0xFF0C0C0C);

那么,那么,这个绘图缓存如何优化绘图速率,又怎么阻碍了Animation的执行?

先看两个方法:

1)ViewGroup -> dispatchDraw(Canvas canvas) 方法:

    /**
     * {@inheritDoc}
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;
     // 关键字:FLAG_RUN_ANIMATION,FLAG_ANIMATION_CACHE,cache,buildCache
        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;

            final boolean buildCache = !isHardwareAccelerated();
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    final LayoutParams params = child.getLayoutParams();
                    attachLayoutAnimationParameters(child, params, i, count);
                    bindLayoutAnimation(child);
                    if (cache) {
                        child.setDrawingCacheEnabled(true);
                        if (buildCache) {
                            child.buildDrawingCache(true);
                        }
                    }
                }
            }

            final LayoutAnimationController controller = mLayoutAnimationController;
            if (controller.willOverlap()) {
                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
            }

            controller.start();

            mGroupFlags &= ~FLAG_RUN_ANIMATION;
            mGroupFlags &= ~FLAG_ANIMATION_DONE;

            if (cache) {
                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
            }

            if (mAnimationListener != null) {
                mAnimationListener.onAnimationStart(controller.getAnimation());
            }
        }

        int saveCount = 0;
        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        if (clipToPadding) {
            saveCount = canvas.save();
            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);

        }

        // We will draw our child‘s animation, let‘s reset the flag
        mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;

        boolean more = false;
        final long drawingTime = getDrawingTime();

        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }

        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size() - 1;
            // Go backwards -- we may delete as animations finish
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }

        if (debugDraw()) {
            onDebugDraw(canvas);
        }

        if (clipToPadding) {
            canvas.restoreToCount(saveCount);
        }

        // mGroupFlags might have been updated by drawChild()
        flags = mGroupFlags;

        if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
            invalidate(true);
        }

        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                mLayoutAnimationController.isDone() && !more) {
            // We want to erase the drawing cache and notify the listener after the
            // next frame is drawn because one extra invalidate() is caused by
            // drawChild() after the animation is over
            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
            final Runnable end = new Runnable() {
               public void run() {
                   notifyAnimationListener();
               }
            };
            post(end);
        }
    }

从中可以看出这个FLAG_ANIMATION_CACHE的作用了,当然还和硬件加速扯上了关系,这里先不补充相关知识,想了解的可以度娘or谷歌。

附:对硬件加速带源码分析的比较好的一篇文章:

Android硬件加速绘制过程源码分析(一)
Android硬件加速绘制过程源码分析(二)——DisplayList录制绘制操作
Android硬件加速绘制过程源码分析(三)——DisplayList的绘制过程
Android硬件加速绘制过程源码分析(四)——离屏硬件缓存HardwareLayer

2)View -> draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法;

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    boolean caching;
    ……………
    final int flags = parent.mGroupFlags;
    …………..
    if ((flags & ViewGroup.FLAG_CHILDREN_DRAWN_WITH_CACHE) != 0 ||
                (flags & ViewGroup.FLAG_ALWAYS_DRAWN_WITH_CACHE) != 0) {
            caching = true;
            // Auto-scaled apps are not hw-accelerated, no need to set scaling flag on DisplayList
            if (mAttachInfo != null) scalingRequired =         mAttachInfo.mScalingRequired;
        } else {
            caching = (layerType != LAYER_TYPE_NONE) || hardwareAccelerated;
        }
    …………..
    if (caching) {
            if (!hardwareAccelerated) {
                if (layerType != LAYER_TYPE_NONE) {
                    layerType = LAYER_TYPE_SOFTWARE;
                    buildDrawingCache(true);
                }
                cache = getDrawingCache(true);
            } else {
                switch (layerType) {
                    case LAYER_TYPE_SOFTWARE:
                        if (useDisplayListProperties) {
                            hasDisplayList = canHaveDisplayList();
                        } else {
                            buildDrawingCache(true);
                            cache = getDrawingCache(true);
                        }
                        break;
                    case LAYER_TYPE_HARDWARE:
                        if (useDisplayListProperties) {
                            hasDisplayList = canHaveDisplayList();
                        }
                        break;
                    case LAYER_TYPE_NONE:
                        // Delay getting the display list until animation-driven alpha values are
                        // set up and possibly passed on to the view
                        hasDisplayList = canHaveDisplayList();
                        break;
                }
            }
        }
     …………………..

}    

从上面的代码可以分析出来,如果不禁止绘图缓存,那么每次绘制子View时都要更新缓存并且将缓存画到画布中。这无疑是多了一步,画一个bitmap,animation需要不停的画所以也就多了很多操作,但是这个缓存不是说是对绘制视图的优化嘛,这个秘密就在View的invalidate中,当子View需要 invalidate时,事实上也是交给父布局去分发的。

    /**
     * This is where the invalidate() work actually happens. A full invalidate()
     * causes the drawing cache to be invalidated, but this function can be called with
     * invalidateCache set to false to skip that invalidation step for cases that do not
     * need it (for example, a component that remains at the same dimensions with the same
     * content).
     *
     * @param invalidateCache Whether the drawing cache for this view should be invalidated as
     * well. This is usually true for a full invalidate, but may be set to false if the
     * View‘s contents or dimensions have not changed.   * 指示在视图刷新时,是否也要刷新绘图缓存,对于一个完全的刷新操作,比如视图内容发生了变化,   * 或者控件尺寸发生变化了,那么应该设置true,但是如果不是二者任何一个,则应该设置为false。
     */
    void invalidate(boolean invalidateCache) {
        if (skipInvalidate()) {
            return;
        }
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
                (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
                (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
            mPrivateFlags |= PFLAG_DIRTY;
            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            //noinspection PointlessBooleanExpression,ConstantConditions
            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
                if (p != null && ai != null && ai.mHardwareAccelerated) {
                    // fast-track for GL-enabled applications; just invalidate the whole hierarchy
                    // with a null dirty rect, which tells the ViewAncestor to redraw everything
                    p.invalidateChild(this, null);
                    return;
                }
            }

            if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom - mTop);
                // Don‘t call invalidate -- we don‘t want to internally scroll
                // our own bounds
                p.invalidateChild(this, r);
            }
        }
    }

接着,咱们再看一个ViewGroup的方法,setPersistentDrawingCache(int drawingCacheToKeep):

    /**
     * Indicates what types of drawing caches should be kept in memory after
     * they have been created.
     *
     * @see #getPersistentDrawingCache()
     * @see #setAnimationCacheEnabled(boolean)
     *
     * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
     *        {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
     *        and {@link #PERSISTENT_ALL_CACHES}
     */
    public void setPersistentDrawingCache(int drawingCacheToKeep) {
        mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
    }

这个方法的作用,便是控制绘图缓存在被创建之后,什么时候使用。

方法的可用参数,系统提供了四个值:

1)PERSISTENT_ANIMATION_CACHE:动画前不可用,动画结束时可用,并保存此时的Cache。

2)PERSISTENT_SCROLLING_CACHE:滚动式不可用,滚动结束时可用,并保存此时的Cache。

3)PERSISTENT_ALL_CACHES:不管在什么时候,都是用缓存。

4)PERSISTENT_NO_CACHE:不适用缓存。

因此,你可以手动的控制AnimationDrawCache(在执行动画前禁用,执行完毕后启用)或者调用这个方法传入适用于相应场景的参数值就可以自动实现控制了。

大概的明白了,有木有!其实我理解的也不深入,都是在别人分析的基础上总结出来,希望对大家有用,也对自己有用。

Over!

参考:

1)Android应用优化(2)View cache的优化
2)关于android ui的优化 view 的绘制速度
3)对View DrawingCache的理解
4)Android View animation - poor performance on big screens
时间: 2024-10-05 23:46:23

Android学习分享:执行某ViewGroup的动画时,子控件太多导致动画执行卡顿的问题的相关文章

【Android开发学习笔记】【第四课】基础控件的学习

通过一个简单的例子来学习下面几种控件: 1.TextView:简单的文本显示控件 2.EditText:可以编辑的文本框 3.Button:按钮 4.Menu:这里指的是系统的Menu 5.Toast:消息提示控件,类似于MFc的tip(不知道理解的对不对) 顺便用到上一次学习的多个Activity之间传递数据的技术,来做一个小的计算乘法的case 步骤: (1)主Activity 和显示结果的 Activity 都采用线性布局,下面是布局文件的源代码: <LinearLayout xmlns:

在layoutsubviews中设置子控件的frame,保证执行alpha和frame动画流畅度

在viewDidLoad中初始化需要的子控件,然后提供改变这些子控件的开放接口,然后使用一个bool变量来保存是否已经设置了子类控件的frame类似 -(void)layoutSubviews { [super layoutSubviews]; #pragma mark 这个变量很重要,如果没有就会导致重复的设置frame导致动画效果很差 if (!_laidOut) { _homeNavigationView.frame = self.bounds; _leftNavigationView.f

Android嵌套滑动控件的冲突解决和ViewPager适配当前子控件高度不留空白的办法

最近项目有一个需求,需要多层可滑动控件的嵌套展示,demo效果如下,demo的下载地址在最后 咋一看好像挺简单啊,不就是一个ScrollView + ViewPager + ListView吗,我开始也这样觉得,也用的这种方式实现,结果始终和效果不对劲.这里总结几点问题: 两个或两个以上的滑动控件嵌套时,如果layout_height采用的是wrap_content会造成内部滑动控件的高度不能正确的计算,会导致内部滑动控件的高度始终为0,除非你用定值设置,比如300dp. 两个相同滑动方向的滑动

android 动态背景的实现以及SurfaceView中添加EditText控件

      首先还是一贯作风,我们先看案例: 静态图看不出来效果,如果用过此软件(扎客)的同学们都知道,她的背景会动.怎么样,是不是觉得很时尚,起码比静态的要好(个人观点).其实实现起来并 不复杂,这个如果让做游戏程序员做简直太小儿科了,这里我说明一点,其实我们做应用的也应该多少了解下游戏编程思维,起码对我们做应用有很好的帮助. 下面我简单介绍下实现方式. 实现原理:自定义一个SurfaceView控件.对之不停的onDraw,使得其背景动起来. 对于SurfaceView如果不了解的同学们麻烦

Android UI开源组件库BottomView ,第三方自定义UI控件

这里分享一个Android的非常经典实用而且简单方便的第三方UI控件库:BottomView(小米的米UI也用到了这个) 实现功能: 可以在底部弹出的View里自定义布局: 可以自定义是否可以触摸外部消失: 可以自定义事件: 可以自定义外围背景是否透明: 可以自定义动画: 如果需要的话,可以强制为顶部View显示 BottomView.jar库文件下载地址:http://download.csdn.net/detail/jay100500/7547055 BottomView的Demo下载地址:

[转]Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件

Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件. 1.为了防止拖动ListView时,在列表末尾重复数据显示.需要加入 HashMap<Integer,View> lmap = new HashMap<Integer,View>();其中Integer为列表位置,View为子项视图,加入数据前首先if (lmap.get(position)==null) ,满足条件时,加入lmap.put(position, convertView

[WP8.1UI控件编程]Windows Phone动画方案的选择

8.1 动画方案的选择 Windows Phone的动画实现方式有线性插值动画(3种类型).关键祯动画(4种类型)和基于帧动画,甚至还有定时器动画,然后动画所改变的UI元素属性可以是普通的UI元素属性,变换特效属性和三维特效属性,面对着这么多的选择,我们要实现一个动画效果该怎么去思考动画实现的思路以及怎么选择实现的技术呢?那么我们这小节会先讲解与动画性能相关的知识,然后再讲解怎么去选择动画的实现方案. 8.1.1 帧速率 帧速率是用于测量显示帧数的量度,测量单位为"每秒显示帧数"(Fr

分享工作中遇到的问题积累经验 事务日志太大导致insert不进数据

原文:分享工作中遇到的问题积累经验 事务日志太大导致insert不进数据 分享工作中遇到的问题积累经验 事务日志太大导致insert不进数据 今天开发找我,说数据库insert不进数据,叫我看一下 他发了一个截图给我 然后我登录上服务器,发现了可疑的地方,而且这个数据库之前有一段经历 在月初的时候这个数据库曾经置疑过,启动不起来 Could not redo log record (163041:116859:5), for transaction ID (0:-1175226963), on

android listview和button,ImageButton等有事件的控件的总结

? 1 2 3 4 public ImageButton(Context context, AttributeSet attrs, int defStyle) {     super(context, attrs, defStyle);     setFocusable(true); } 在listview中(或者ExpandableListview),item的view会被进行特殊的处理,通过convertview可以减少解析xml文件,提高效率.但是如果你自己解析一次,然后用变量保存,那么只