首页 文章 精选 留言 我的

精选列表

搜索[卡顿],共9801篇文章
优秀的个人博客,低调大师

Android 卡顿优化 4 布局优化实际技巧

今天分享一些layout布局书写中的一些技巧,希望看过之后你也一样可以写出性价比高的布局。我个人的目标是用最少的View写出一样效果的布局。因为我相信View的数量减少伴随着的就是层级的减少。从而达到结构清晰,渲染速度快的效果。顺着这个逻辑,我将优化分为重用、合并、按需载入。 重用 < include/> < include>标签可以在一个布局中引入另外一个布局,这个的好处显而易见。类似于我们经常用到的工具类,随用随调。便于统一修改使用。 举例说明:首先写一个公共的布局title_bar.xml,app中常用的标题栏。 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:background="@color/background" android:layout_height="48dp"> <ImageView android:layout_width="wrap_content" android:layout_height="match_parent" android:paddingLeft="15dp" android:paddingRight="15dp" android:src="@drawable/icon_back"/> <TextView tools:text="标题" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="18sp" android:textColor="@color/white" /> <TextView tools:text="确定" android:layout_width="wrap_content" android:gravity="center" android:layout_height="match_parent" android:layout_alignParentRight="true" android:paddingLeft="15dp" android:paddingRight="15dp" android:textSize="16sp" android:textColor="@color/white" /> </RelativeLayout> 预览: 下来activity_main.xml调用它: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/title_bar"/> </RelativeLayout> 运行后效果和预览一样一样的。当然我们也可以在< include>标签当中重新设置宽高等layout属性。 合并 减少嵌套 首先我们心中要有一个大原则:尽量保持布局层级的扁平化。在这个大原则下我们要知道: 在不影响层级深度的情况下,使用LinearLayout而不是RelativeLayout。因为RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,才会让子View调用2次onMeasure。Measure的耗时越长那么绘制效率就低。 如果非要是嵌套,那么尽量避免RelativeLayout嵌套RelativeLayout。这简直就是恶性循环,丧心病狂。 实现方法就不细说了,大家都是明白人。 < merge/> < merge/>主要用来去除不必要的FrameLayout。它的使用最理想的情况就是你的根布局是FrameLayout,同时没有使用background等属性。这时可以直接替换。因为我们布局外层就是FrameLayout,直接“合并”。 举例说明:比如上面用到的activity_main.xml文件,我们通过View Hierarchy工具看一下,如图: 可以看到,最外层是FrameLayout,下来我们修改一下。 <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/title_bar"/> </merge> 再次查看: 很明显少了一层RelativeLayout,当然运行效果是一样的。当然如果我们不需要title_bar.xml中的绿色背景,那么可以这样修改。 <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="wrap_content" android:layout_height="48dp" android:paddingLeft="15dp" android:paddingRight="15dp" android:src="@drawable/icon_back_1"/> <TextView android:layout_gravity="center_horizontal" android:text="标题" android:gravity="center" android:layout_width="wrap_content" android:layout_height="48dp" android:layout_centerInParent="true" android:textSize="18sp" android:textColor="@color/black" /> <TextView android:text="确定" android:layout_gravity="right" android:layout_width="wrap_content" android:gravity="center" android:layout_height="48dp" android:layout_alignParentRight="true" android:paddingLeft="15dp" android:paddingRight="15dp" android:textSize="16sp" android:textColor="@color/black" /> </merge> 运行效果: 运行查看层级,如下图: 结果很明显。 用TextView同时显示图片和文字 这个我就不细说了,举一个我们项目中的一个例子,代码一看便知。 首先要完成的效果是如下图: 这种效果很常见,一般实现方法是这样。(貌似没人这样写吧,哈哈) <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="horizontal" android:background="@color/white" android:layout_width="match_parent" android:layout_height="50dp"> <ImageView android:layout_marginLeft="10dp" android:layout_width="wrap_content" android:src="@drawable/icon_1" android:layout_height="match_parent" /> <TextView android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="我的卡券" android:gravity="center_vertical" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" /> <ImageView android:layout_marginRight="10dp" android:src="@drawable/icon_4" android:layout_width="wrap_content" android:layout_height="match_parent"/> </LinearLayout> </LinearLayout> 效果图: 那么我们优化一下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:drawableLeft="@drawable/icon_1" android:drawableRight="@drawable/icon_4" android:drawablePadding="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="我的卡券" android:background="@color/white" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="50dp" /> </LinearLayout> 你没有看错,少了两个ImageView和去除嵌套LinearLayout。效果不用说一样一样的。当然EditView等也一样的,还有属性drawableBottom和drawableTop供你使用。同时利用代码setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)可以让我们动态去设置图片。 使用TextView的行间距 先上我们需要实现的效果图: 效果很简单,实现代码: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="100dp" android:background="@color/white" android:layout_width="match_parent" xmlns:tools="http://schemas.android.com/tools"> <ImageView android:padding="25dp" android:src="@drawable/kd_1" android:layout_width="100dp" android:layout_height="100dp"/> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="100dp"> <TextView tools:text="揽件方式:上门取件" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="25dp"/> <TextView tools:text="快递公司:顺丰快递" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="25dp"/> <TextView tools:text="预约时间:9月6日 立即取件" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="25dp"/> <TextView tools:text="快递费用:等待称重确定价格" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="25dp"/> </LinearLayout> </LinearLayout> 这里我偷懒了多嵌套了一层LinearLayout,但。。。这不重要,我先直接修改。 优化后代码: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="100dp" android:background="@color/white" android:layout_width="match_parent"> <ImageView android:padding="25dp" android:src="@drawable/kd_1" android:layout_width="100dp" android:layout_height="match_parent"/> <TextView android:textSize="14dp" android:lineSpacingExtra="8dp" android:gravity="center_vertical" android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> 老规矩,效果一样一样的。可以看到我们仅仅利用android:lineSpacingExtra="8dp"这一行代码就省去了3个TextView,如果行数更多呢?是不是方便多了。 其中:lineSpacingExtra属性代表的是行间距,他默认是0,是一个绝对高度值。同时还有lineSpacingMultiplier属性,它代表行间距倍数,默认为1.0f,是一个相对高度值。我们来使用一下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="100dp" android:background="@color/white" android:layout_width="match_parent"> <ImageView android:padding="25dp" android:src="@drawable/kd_1" android:layout_width="100dp" android:layout_height="100dp"/> <TextView android:textSize="14dp" android:lineSpacingMultiplier="1.3" android:gravity="center_vertical" android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> 当然了这两条属性可以同时使用,查看源码可以知道,他们的高度计算规则为mTextPaint.getFontMetricsInt(null) * 行间距倍数 + 行间距。 使用Spannable或Html.fromHtml 这里也是举例说明,比如下图效果: 如果实现上图红框中的效果,笨办法就是写三个TextView,“¥”,“价格”,“门市价”分别实现,其实用一个TextVIew就可以实现,类似如下代码: String text = String.format("¥%1$s 门市价:¥%2$s", 18.6, 22); int z = text.lastIndexOf("门"); SpannableStringBuilder style = new SpannableStringBuilder(text); style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号 style.setSpan(new ForegroundColorSpan(Color.parseColor("#afafaf")), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //颜色 style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号 tv.setText(style); 同样Html.fromHtml也可以实现。这样不就减少了两个TextView了。 按需载入 ViewStub 在开发中经常会遇到这样的情况,会在程序运行时动态根据条件来决定显示哪个View或某个布局。那么通常做法就是把用到的View都写在布局中,然后在代码中动态的更改它的可见性。但是它的这样仍然会创建View,会影响性能。 这时就可以用到ViewStub了,ViewStub是一个轻量级的View,不占布局位置,占用资源非常小。 例子:比如我们请求网络加载列表,如果网络异常或者加载失败我们可以显示一个提示View,上面可以点击重新加载。当然一直没有错误时,我们就不显示。 <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > …… <ViewStub android:layout_gravity="center" android:id="@+id/hint_view" android:layout_width="match_parent" android:inflatedId="@+id/hint_view" android:layout_height="wrap_content" android:layout="@layout/hint_view"/> </merge> hint_view.xml就是这个提示View,可以根据情况自己写。 用法: private View hintView; if (网络异常。。。) { if (hintView == null) { ViewStub viewStub = (ViewStub)this.findViewById(R.id.hint_view); hintView = viewStub.inflate(); TextView textView = (TextView) hintView.findViewById(R.id.tv); textView.setText("网络异常!"); } hintView.setVisibility(View.VISIBLE); }else{ if (hintView != null) { hintView.setVisibility(View.GONE); } } 用法很简单,记得一旦ViewStub可见或是被inflate了,ViewStub就不存在了,取而代之的是被inflate的Layout。所以它也被称做惰性控件。 其他小技巧 用LinearLayout自带的分割线 还记得上文用TextView同时显示图片和文字中的例子吗?我们可以看到每个条目之间都是有一根分隔线的,那么怎么实现呢?别人我不知道,反正我原来是用一个View设置高度实现的。相信一定有人和我一样。 那么老办法我就不演示了,直接上代码: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:divider="@drawable/divider" android:showDividers="middle"> <TextView android:drawableLeft="@drawable/icon_1" android:drawableRight="@drawable/icon_4" android:drawablePadding="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="我的卡券" android:background="@color/white" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="50dp" /> <TextView android:drawableLeft="@drawable/icon_2" android:drawableRight="@drawable/icon_4" android:drawablePadding="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="地址管理" android:background="@color/white" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="50dp" /> <TextView android:drawableLeft="@drawable/icon_3" android:drawableRight="@drawable/icon_4" android:drawablePadding="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="检查更新" android:background="@color/white" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="50dp" /> </LinearLayout> 效果图: 实现的核心部分其实是LinearLayout的这两行。 android:divider="@drawable/divider" android:showDividers="middle" 其中divider.xml是分隔线样式。 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <size android:width="1dp" android:height="1dp"/> <solid android:color="#e1e1e1"/> </shape> showDividers 是分隔线的显示位置,beginning、middle、end分别代表显示在开始位置,中间,末尾。 还有dividerPadding属性这里没有用到,意思很明确给divider添加padding。感兴趣可以试试。 Space控件 还是接着上面的例子,如果要给条目中间添加间距,怎么实现呢?当然也很简单,比如添加一个高10dp的View,或者使用android:layout_marginTop="10dp"等方法。但是增加View违背了我们的初衷,并且影响性能。使用过多的margin其实会影响代码的可读性。 这时你就可以使用Space,他是一个轻量级的。我们可以看下源码: /** * Space is a lightweight View subclass that may be used to create gaps between components * in general purpose layouts. */ public final class Space extends View { /** * {@inheritDoc} */ public Space(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); if (getVisibility() == VISIBLE) { setVisibility(INVISIBLE); } } /** * {@inheritDoc} */ public Space(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } /** * {@inheritDoc} */ public Space(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * {@inheritDoc} */ public Space(Context context) { //noinspection NullableProblems this(context, null); } /** * Draw nothing. * * @param canvas an unused parameter. */ @Override public void draw(Canvas canvas) { } /** * Compare to: {@link View#getDefaultSize(int, int)} * If mode is AT_MOST, return the child size instead of the parent size * (unless it is too big). */ private static int getDefaultSize2(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: result = Math.min(size, specSize); break; case MeasureSpec.EXACTLY: result = specSize; break; } return result; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension( getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec)); } } 可以看到在draw方法没有绘制任何东西,那么性能也就几乎没有影响。 实现代码与效果: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:divider="@drawable/divider" android:showDividers="middle|beginning|end"> <TextView android:drawableLeft="@drawable/icon_1" android:drawableRight="@drawable/icon_4" android:drawablePadding="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="我的卡券" android:background="@color/white" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="50dp" /> <TextView android:drawableLeft="@drawable/icon_2" android:drawableRight="@drawable/icon_4" android:drawablePadding="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="地址管理" android:background="@color/white" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="50dp" /> <Space android:layout_width="match_parent" android:layout_height="15dp"/> <TextView android:drawableLeft="@drawable/icon_3" android:drawableRight="@drawable/icon_4" android:drawablePadding="10dp" android:paddingLeft="10dp" android:paddingRight="10dp" android:textSize="16sp" android:text="检查更新" android:background="@color/white" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="50dp" /> </LinearLayout> 防止过度绘制 这个完全可以看鸿洋大神这篇 《Android UI性能优化实战 识别绘制中的性能问题》:http://blog.csdn.net/lmj623565791/article/details/45556391 参考 Android布局优化:http://www.lightskystreet.com/2015/01/19/android-layout-optimize 总结 最后想想如果没有用这些技巧,我们要写多少代码,多少View?效果是不是杠杠的!其实上面说了这么多,具体的情景使用还是要看项目的具体情况,在性能面前有些还是要取舍的,但千万不能为了优化而优化。优化也是不断积累的过程,不要指望立竿见影。愿大家都能写出一手漂亮的布局。最后觉得不错的点个赞哈! 本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/8483663.html,如需转载请自行联系原作者

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

Android 卡顿优化 3 布局优化 工具 Hierarchy Viewer

欲善其事, 先利其器. 分析布局, 就不得不用到Hierarchy Viewer了. 本文工具使用皆以GithubApp的详情界面RepoDetailActivity为例说明. 为了不影响阅读体验, 对应的布局文件activity_repo_detail.xml的代码放在文末 1, Hierarchy Viewer怎么用 Hierarchy发音 [美: 'haɪərɑrki] [英: 'haɪərɑːkɪ] 层次结构的意思. 之前一直念不顺这个单词Hierarchy, 就简称为H Viewer了. 下文就这么简称吧. 如官网描述, H Viewer是用来分析调试和优化我们的UI的一个图形化工具. 它会展示当前界面的View层级. 1.1 启用H Viewer 比较早接触Android开发的同学可能知道, H Viewer只能在root过的机器才能使用. 主要是在没有root过的机器中view server这个服务是没有开启的. H Viewer就无法连接到机器获取view层级信息. 正所谓高手在民间, 大家都尝试在未root的机器中启用view server来使用H Viewer. 最具代表性的就是romainguy的ViewServer, 只需集成少量代码到你的Activity, 相当于在手机端开启了view server服务, 建立socket通道与PC端的H Viewer通信. 此工程被Android官网吸收, 作为开启H View的方案之一. 完整开启H Viewer的套路如下: 手机开启开发者模式, USB调试. 根据手机的Android系统版本: 4.0及以下, 没有root. 使用上述的开源工程ViewServer提供的方式. 4.0及以下, 已经root. 无需其他额外设置. 4.1及以上. 需要在PC端设置ANDROID_HVPROTO环境变量. 设置系统环境变量: ANDROID_HVPROTO, 值为ddm 具体设置系统环境变量根据PC系统不同而异. 做完上述配置后, 你就可以打开H Viewer了, 打开DDMS, 如下操作进入H Viewer界面: ddms_open_hviewer 1.2 H Viewer界面详解 以GithubApp的详情界面RepoDetailActivity为例说明: Snip20160902_1.png 界面分为四个部分: Window 显示当前连接的设备和供分析的界面. 可手动选择. Tree View 树状图的形式展示该Activity中的View层级结构. 可以放大缩小, 每个节点代表一个View, 点击可以弹出其属性, 当前值, 并且在LayoutView中会显示其在界面中相应位置. Tree View是我们主要要分析的视图. Tree Overview Tree View的概览图. 有一个选择框, 可以拖动选择查看. 选中的部分会在Tree View中显示. Layout View 匹配手机屏幕的视图, 按照View的实际显示位置展示出来的框图. 1.3 H Viewer参数解读 通过Tree View可以很直观的看到View的层级. 点击Tree View的RepoItemView这个节点: 14728281715494.jpg 关于三个小圆点的性能指示, 在App优化之性能分析工具一文中有提到, 再强调一遍: 三个小圆点, 依次表示Measure, Layout, Draw, 可以理解为对应View的onMeasure, onLayout, onDraw三个方法. 绿色, 表示该View的此项性能比该View Tree中超过50%的View都要快. 黄色, 表示该View的此项性能比该View Tree中超过50%的View都要慢. 红色, 表示该View的此项性能是View Tree中最慢的. 如果你的界面的Tree View中红点较多, 那就需要注意了. 一般来说: 1, Measure红点, 可能是布局中嵌套RelativeLayout, 或是嵌套LinearLayout都使用了weight属性. 2, Layout红点, 可能是布局层级太深. 3, Draw红点, 可能是自定义View的绘制有问题, 复杂计算等. 由上图, 可以看到我们的RepoItemView的三项指标都不合格, 证明其还有很多优化空间. 层级, 绘制都可以优化. 除了用H Viewer来做代码后分析, Android还提供了Lint, 在我们编写xml布局文件时就即时的给出一些相关提示. 2, Lint tool 打开RepoDetailActivity的布局文件activity_repo_detail.xml, 在Android Studio菜单栏中开启Lint检查: 14728313149102.jpg 选择当前文件: 14728313382536.jpg 会在下方弹出分析结果: 14728314908964.jpg 分析结果包括用法检测(例如版本特有属性), 国际化(字符串是否提取到strings.xml, Rlt支持等), 以及我们今天的主题---性能分析结果. 点开"Android -> Lint -> Performance"项, 可以看到关于布局性能的建议项. 此例中是说ScrollView的父级LinearLayout是不必要的. 3, 怎么优化你的布局 通过以上工具的使用和分析, 也基本能找到布局的一些常见的好与不好的了. 正所谓授之以鱼不如授之以渔. 在此也就不太详细去讲怎么优化了, 几点建议, 大家自行实践吧:) 尽量减少布局层级和复杂度 尽量不要嵌套使用RelativeLayout. 尽量不要在嵌套的LinearLayout中都使用weight属性. Layout的选择, 以尽量减少View树的层级为主. 去除不必要的父布局. 善用TextView的Drawable减少布局层级 如果H Viewer查看层级超过5层, 你就需要考虑优化下布局了~ 善用Tag <include> 使用include来重用布局. <merge> 使用<merge>来解决include或自定义组合ViewGroup导致的冗余层级问题. 例如本例中的RepoItemView的布局文件实际可以用一个<merge>标签来减少一级. <ViewStub> ListView优化 contentView复用 引入holder来避免重复的findViewById. 分页加载 4, 附示例代码 因github上的源码会持续更新, 特留对应代码在此. activity_repo_detail.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/root_layout" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/md_white_1000" android:orientation="vertical" android:padding="@dimen/dimen_10"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:scrollbars="none"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <com.anly.githubapp.ui.widget.RepoItemView android:id="@+id/repo_item_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/md_grey_300" android:elevation="@dimen/dimen_2"/> <LinearLayout android:id="@+id/contributor_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dimen_10" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical" android:orientation="horizontal" android:background="@drawable/button_bg" android:paddingLeft="@dimen/dimen_10"> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:text="{oct-organization} Contributors"/> <TextView android:id="@+id/contributors_count" android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical"/> </LinearLayout> <android.support.v7.widget.RecyclerView android:id="@+id/contributor_list" android:layout_width="match_parent" android:layout_height="@dimen/dimen_60" android:layout_marginTop="@dimen/dimen_2" /> </LinearLayout> <LinearLayout android:id="@+id/fork_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dimen_10" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical" android:orientation="horizontal" android:background="@drawable/button_bg" android:paddingLeft="@dimen/dimen_10" > <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:text="{oct-gist_fork} Forks"/> <TextView android:id="@+id/forks_count" android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical"/> </LinearLayout> <android.support.v7.widget.RecyclerView android:id="@+id/fork_list" android:layout_width="match_parent" android:layout_height="@dimen/dimen_60" android:layout_marginTop="@dimen/dimen_2" /> </LinearLayout> <LinearLayout android:id="@+id/code_layout" android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical" android:orientation="horizontal" android:layout_marginTop="@dimen/dimen_10" android:background="@drawable/button_bg" android:paddingLeft="@dimen/dimen_10"> <TextView android:id="@+id/code_label" android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical" android:text="{oct-file_code} Code"/> </LinearLayout> <LinearLayout android:id="@+id/readme_layout" android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical" android:orientation="horizontal" android:layout_marginTop="@dimen/dimen_10" android:background="@drawable/button_bg" android:paddingLeft="@dimen/dimen_10"> <TextView android:id="@+id/readme_label" android:layout_width="match_parent" android:layout_height="@dimen/dimen_40" android:gravity="center_vertical" android:text="{oct-info} README"/> </LinearLayout> </LinearLayout> </ScrollView> </LinearLayout> com.anly.githubapp.ui.widget.RepoItemView对应的布局: <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="@dimen/dimen_10"> <TextView android:id="@+id/name" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="left|center_vertical" android:maxLines="1" android:text="@string/app_name" android:textColor="@android:color/black" android:textSize="@dimen/text_size_18"/> <TextView android:id="@+id/desc" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="left|center_vertical" android:maxLines="2" android:text="@string/app_name" android:textColor="@android:color/darker_gray" android:textSize="@dimen/text_size_12"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dimen_5" android:gravity="center_vertical" android:orientation="horizontal"> <ImageView android:id="@+id/image" android:layout_width="@dimen/dimen_32" android:layout_height="@dimen/dimen_32" android:scaleType="centerInside" android:src="@mipmap/ic_launcher" android:visibility="visible"/> <TextView android:id="@+id/owner" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="@dimen/dimen_10" android:gravity="left|center_vertical" android:text="@string/app_name" android:textColor="@android:color/black" android:textSize="@dimen/text_size_14"/> </LinearLayout> <View android:layout_marginTop="@dimen/dimen_5" android:layout_width="match_parent" android:layout_height="1px" android:background="@color/grey"/> <LinearLayout android:layout_width="match_parent" android:layout_height="@dimen/dimen_32" android:gravity="center_vertical" android:orientation="horizontal" android:paddingTop="@dimen/dimen_10"> <TextView android:id="@+id/update_time" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="left|center_vertical" android:text="@string/app_name" android:textColor="@android:color/black" android:textSize="@dimen/text_size_12" /> <View android:layout_width="1px" android:layout_height="match_parent" android:background="@color/grey"/> <LinearLayout android:id="@+id/star_view" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:orientation="horizontal"> <ImageView android:id="@+id/star_icon" android:layout_width="@dimen/dimen_16" android:layout_height="@dimen/dimen_16" android:scaleType="centerInside" android:src="@drawable/ic_star"/> <TextView android:id="@+id/star" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginLeft="@dimen/dimen_5" android:gravity="center" android:text="@string/app_name" android:textColor="@android:color/black" android:textSize="@dimen/text_size_12" /> </LinearLayout> </LinearLayout> </LinearLayout> <com.flyco.labelview.LabelView android:id="@+id/label_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" app:lv_background_color="@color/md_yellow_500" app:lv_gravity="TOP_RIGHT" app:lv_text="TEST" app:lv_text_size="@dimen/text_size_12"/> </FrameLayout> 优化不同于做功能, 可能分析的多, 出的成果少~ 比较枯燥, 然而优化也是App发展的必经之路, 欢迎大家分享经验. 作者:一点点征服 出处:http://www.cnblogs.com/ldq2016/ 本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利 本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/8483636.html,如需转载请自行联系原作者

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

告别卡顿与等待,Rancher Vai 让集群操作“秒响应”

如果你正用 Rancher 在大规模环境中管理 Kubernetes,那么你一定知道,UI 性能不只是"锦上添花"------它对效率至关重要。Rancher 团队一直在不断努力提升平台在更复杂环境下的处理能力。 在这篇文章里,我们将深入探讨一个令人兴奋、正不断演进的改进项目:代号为 "Vai"(也称 UI 服务器端分页或基于 SQLite 的缓存)。目标是让你在使用 Rancher 时拥有更流畅、更快速、更具可扩展性的体验,尤其在你要管理大量 Kubernetes 资源的场景下。正因如此,Rancher 团队构建了 Vai 项目,该项目现已成为 SUSE Rancher Prime 中的一项生产就绪功能。 Rancher Prime 的 Vai 引擎通过重新设计数据流向 UI 的方式,实现更快、更可靠的 Kubernetes 管理。这保证了企业能够更顺畅地管理大型复杂环境,同时降低成本、提高效率。 更快更顺畅的 Rancher UI --- 提升效率 减轻 Kubernetes API 服务器的压力 --- 保持集群健康、提升性能 默认启用加密的安全缓存 --- 保护敏感数据 可扩展的基础设施 --- 随环境增长而扩展,并为未来洞察提供可能 问题所在:规模如何影响 UI 性能 随着 Rancher 部署规模的扩大,管理海量集群、节点及其内部资源成为常态。我们明确听到了用户的反馈:UI 性能,尤其是 Rancher Dashboard 的响应,在某些极限场景下已成为瓶颈。 为应对这一挑战,我们为 Rancher 的性能设定了一些目标。比如,为了保证 UI 在极端规模场景下也能正常应对,我们将一个基准目标设为:对某一种资源(以载荷 1MiB 的 ConfigMap 为例),能够分页显示数万个条目;在最坏情况下,分页结果从 API 返回时间不超过 0.5 秒,以便最终渲染在 1 秒之内完成。 这些数字并不仅仅是理论目标;它们映射了真实场景中的痛点:一个迟缓的 UI 会妨碍你有效地管理和监控 Kubernetes 环境。正如我们在内部所说:"当 Dashboard 无法达到用户期望时,用户对 Rancher 的印象就会变差,使用效率也会降低。" 这正是我们决心解决的关键痛点。 问题的核心通常归结于 UI 如何获取与处理大量 Kubernetes 对象列表。将数千个项(如 Pod、Deployment 或其他资源)拉入浏览器端进行缓存、排序与过滤,会对网络、浏览器内存造成压力,并导致明显延迟。此外,这样的操作会对 Kubernetes API Server 造成过度负载,从而对被管理集群产生负面影响。 因此,我们知道,必须设计一种更智能、更高效的数据交付方式给 UI。 解决方案:Vai 如何带来更快、可扩展的 UI 这正是 "Vai" 问世的原因。这个项目不仅仅是一次小优化,而是对 Rancher 内部 API(名为 Steve)如何向 UI 提供资源、以及其如何在大规模场景下与 Kubernetes 数据交互的根本性重构。 Vai 的主要目标是在服务器端实现高效的分页、过滤和排序。与其将庞大的数据集拉到浏览器端再处理,不如让服务器端先行处理这些操作,只将必要、已经处理过的那一页数据发送给 UI。 核心思路是构建一个位于 Kubernetes API Server 和 Rancher UI 之间的强大缓存层。这个缓存层以 SQLite 为后端存储,使 Steve(Rancher 的 Kubernetes API 代理组件)能够更快地响应 UI 对对象列表的请求,因为排序、过滤和分页操作都在服务器端完成,从而最小化数据传输量。 期望成果是什么?响应速度大幅提升的 UI、Kubernetes API Server 和 Rancher 本身负载显著降低、在资源分页浏览时获得更好的体验------即便在有数万个资源的环境中也能流畅应对。 技术深潜:Vai 的 SQLite 支撑架构 那么,Vai 到底是如何运作的?其"魔法"在于它的架构:将 Kubernetes 原生的 Informer 机制与 SQLite 的高速、低内存开销结合起来。 Vai 是对 Steve 的一种扩展,Steve 是 Rancher 用来将 UI 的 API 请求代理到 Kubernetes 的组件。Vai 利用专门的 Informer 来缓存 Kubernetes API 的信息。 如果你对 Kubernetes 控制器开发稍有了解,就知道 Informer 是一种标准机制,用于监听资源变动并维护本地同步缓存。当 UI 第一次请求某一种资源(例如 Pods)的列表时,会为该资源类型创建一个 Vai Informer。这个 Informer 首先向 Kubernetes API 发起 LIST 请求,然后再开启 WATCH,以保持缓存与 Kubernetes 实时同步。 关键区别在于:传统 Informer 缓存通常驻留在内存中,而 Vai Informer 使用 SQLite 作为其后端持久化存储。这意味着被缓存的 Kubernetes 对象被持久化写入磁盘(默认情况下,会写入 Rancher pod 或下游集群的 cattle-cluster-agent pod 内)。 与纯内存解决方案相比,磁盘存储提供了更大的缓存容量。这在处理我们测试的最坏情况时至关重要,例如前面提到的 80,000 个大型 ConfigMap 基准测试案例中约 80 GiB 的数据。 下面是一个简化流程: UI 请求:浏览器端(Rancher Dashboard 的 JavaScript 代码)向 Steve 的某个 API 端点请求一页 Pods,带有排序(如按创建时间)和过滤(如按名称)条件 Steve 与 Vai:Steve 接收到请求。如果对应资源类型的 Vai 缓存已被预热(即缓存已就绪),则 Steve 会将该请求转换成一个 SQL 查询 SQLite 查询:该 SQL 查询在 SQLite 数据库中执行,SQLite 引擎高效地完成过滤、排序、分页,只挑选出请求页所需的数据 响应 UI:Steve 将这页整理好的数据返回给 UI 美妙之处在于:后续对不同页、或对同一种资源做略有不同的过滤/排序请求,都可以直接在本地 SQLite 缓存中处理,而无需再次对 Kubernetes API Server 发起完整的 LIST 请求。这样便极大地减轻了对 kube-apiserver 和 etcd 的访问压力。 数据库本身为每种资源类型设计了一组表。通常包括一个"对象表"(object table),存储完整的资源对象 blob;以及一个"字段表"(fields table),包含可供排序或过滤使用的索引列,如 name、namespace、creation timestamp 以及其他模式定义属性。这个 "感兴趣字段"(fields of interest) 的概念至关重要。我们不会为每个对象的每个字段都建立索引,那样效率太低;相反,我们只索引 UI 交互中最常用的字段。这是在常见用例和索引开销之间的一个权衡。 Vai 的演进历程与替代方案回顾 Vai 的开发是一次迭代的旅程,从 HackWeek 项目起步,经过多轮设计讨论、RFC 制定、实现和优化。Rancher 2.8 提供了起步的基础能力,随着版本演进(2.9、2.10、2.11,直至 2.12),我们不断在其之上构建、修正与完善。沿途我们通过多个 EPIC GitHub issue 跟踪进展、修复 bug、调整实现,并解决诸如缓存预热性能、功能一致性等挑战。这个迭代方式使我们能够渐进交付,并依据反馈做及时调整。 在 Vai 之前,我们并非第一次尝试通过缓存优化 Steve 的 UI 性能。之前我们试过多种策略,并对它们做了系统化的评估和基准测试,最终选择了当前设计。 下面是几种我们在 Vai 之前探索过的替代方案,以及它们的利弊: | 替代方案 | 初步收益 | 主要痛点 | 它如何引导我们走向 Vai 的设计 | | --------------------------- | ------------------------ | --------------------------------------------------------------------------------- | ----------------------------------------------- | | Steve 原本的内存 LRU 缓存 | 作为 UI 性能基线 | 由于对每种查询参数变化或 RBAC 权限变化都可能产生新的缓存条目,导致内存效率低、数据重复 | 突显出需要一种更高效的缓存机制,以避免冗余数据存储,并能应对多样的 UI 请求而不消耗过多内存 | | 在从 Kubernetes API 返回后再做内存缓存 | 通过缓存原始 API 响应可略有提高效率 | 缓存频繁失效,因为即便元素未发生变化,资源版本(resourceVersion)也可能改变,从而导致每次"最新"请求都写入新的缓存条目 | 暴露出需要一种对资源版本频繁变化更具鲁棒性的缓存策略 | | 为内存缓存追踪 resourceVersion | 在不常变化的列表上,内存使用减少且响应一致性提升 | 对于频繁更新的资源(如 leader election leases),缓存几乎总被认为"过期",导致不断重新获取和加入新条目,对于包含数万个资源的动态场景不可行 | 促使我们考虑引入 Informer 机制以实现实时高效更新 | 这段历程展示了我们严谨的工程流程:我们并不是随意选定一个方案,而是识别已有方案的局限性,逐步推进到更健壮的架构。频繁更新的资源导致缓存抖动,是一个非常关键的推动因素。 Vai 正是为直接应对这些问题而选中的方案:通过 Informer 获取高效实时更新,通过 SQLite 获得更大、磁盘级持久缓存,并且能运用 SQL 提供服务器端排序、过滤、分页。这样便有效地将部分工作负载从 Kubernetes API Server 和客户端浏览器中卸载。 对你(工程师)而言,Vai 的意义 那这一系列变革,对你这个使用 Rancher 的技术工程师来说,有什么实打实的好处? 异常灵敏的 UI 体验:最直接的收益是,当你在 Cluster Explorer 中浏览成千上万条资源(如 Pods、Secrets、ConfigMaps 等)时,排序、过滤、翻页操作会感觉非常迅捷流畅,因为计算已在服务器端完成。 Kubernetes API 服务器负担减轻:由于大量 UI 数据请求可以直接从缓存返回,Vai 显著减少 Rancher 向下游 API 服务器发起的 LIST 和 GET 请求次数。这不仅对 Rancher 有利,也能改善被管理集群的健康和性能,为你的实际工作负载释放更多 API Server 资源。 图 1:Rancher 2.11.1 上的 kubapiserver 负载,这是 QA 测试的一部分。Steve 的 "list" 负载由 k6 生成,然后 Steve 又加载 Kubernetes API Server,此处由 Rancher Monitoring 进行监控。 图 2:Rancher 2.12.1(启用 Vai)上的 kubapiserver 负载,这是 QA 测试的一部分。更重的 Steve "list" 负载由 k6 生成(比上图高出 10 倍),而 Kubernetes API Server 的负载几乎未受影响。 为更丰富的数据洞察建立基础:使用 SQLite 为后端存储为未来 Rancher 版本提供了 SQL 引擎的灵活性。未来可以做更复杂的查询,比如在服务器端直接 JOIN 多种 Kubernetes 资源进行汇总视图。以前在无 Vai 的情况下,这类操作通常需要多个大型 LIST,然后在浏览器端进行映射、JOIN,资源消耗极大。 Rancher 更具可扩展性:这些效率提升意味着 Rancher 自身可以更有效地管理更多、更大的集群。其控制平面组件(如 Steve)也变得更加节省资源。 效率提升,减少等待:更快、更可靠的 UI 意味着你等待的时间更少,能做的事情更多。这正是我们设计 Vai 的出发点之一:避免一个缓慢的 Dashboard 给用户留下不好的印象并削弱生产效率。 总之,Vai 是一个基础性组件,能帮助 Rancher 随着你的需求扩展。它确保即便你的 Kubernetes 规模增长得很大,Rancher 仍然是一款强劲、高性能的工具。同时,这种改进也为未来更高级的功能铺路------响应式的数据访问层常常是许多高级特性的前提。 安全考虑:保护你的数据 谈到数据管理,特别是来自你 Kubernetes 集群的数据,安全问题至关重要。因为 Vai 会将 Kubernetes 对象的副本缓存到 Rancher server pod(对于本地集群)或 cattle-cluster-agent pod(对于下游集群)的磁盘上,我们在设计时就把"静态加密"(encryption-at-rest)纳入了架构之中。 具体来说,我们内建了两道防线: 可选的全缓存加密 :若你对 SQLite 缓存中的数据在磁盘上的安全性有顾虑,可以通过在相应的 Rancher 或 agent pod 中设置环境变量 CATTLE_ENCRYPT_CACHE_ALL=true 来启用对所有缓存对象的加密。 Secrets 和 Tokens 始终加密:无论你是否开启全缓存加密,Kubernetes Secrets 和 Rancher Tokens 在缓存中始终被加密。这为最敏感的信息提供了最低保护门槛。 加密机制本身采用 AES-GCM,使用一个驻内存的 Key-Encryption Key(KEK)来加密 / 解密 Data Encryption Keys(DEKs)。这些 DEK 用于加密实际资源数据,并且定期轮换以增强安全性。 此外,还有一个重要的安全考量是访问控制(RBAC、权限控制等):由于我们改变了缓存逻辑,必须确保政策和权限机制在启用 Vai 后仍然得到尊重。为此,我们与安全团队密切合作,执行深入的审查和自动化测试(启用与未启用 Vai 的场景同时测试),以确保一致性。 Rancher 性能的未来之路 Vai 代表了 Rancher 在大规模数据处理方面的一次重大进步,但这并不是终点。这个特性正走在成为 Rancher 体验核心的一条路径上。 在 Rancher 2.11 及之前的版本中,Vai 被视为实验性功能 ,默认关闭。但令人振奋的是,从 Rancher 2.12 开始,Vai 被认为是可用于生产环境的特性,并在默认情况下被启用且受 SUSE 支持。对我们来说,这可是一个重要里程碑,我们将继续根据真实使用反馈来增强它。 更进一步地,我们才刚刚开始挖掘底层 SQL 引擎所提供的额外灵活性。我们预期会利用这一基础,在未来丰富 Rancher 的数据摘要和洞察功能。我们的终极目标是,在未来的某个版本中,让这个强大的性能引擎默认开启,使得高度可扩展的 UI 成为每个人的标准体验。 帮我们测试与完善 Vai! 你的反馈非常重要!如果你愿意深度体验并帮忙验证,建议你先阅读 Rancher 文档中关于 UI Server-Side Pagination 的特性页面,然后将你的体验反馈给我们:在 GitHub 上开一个 Issue,分享你的发现和建议。 感谢你的阅读!我们期待听到你的使用体验,一起打造更加快速、可扩展的 Rancher 平台。

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

告别SQL卡顿与混乱!AI如何赋能实时计算?

在当今数据驱动的商业环境中,SQL作为与数据库交互的核心语言,其编写效率和质量直接影响着企业的数据决策速度和系统性能。然而,我们在长期的企业服务实践中发现,数据库开发人员普遍面临以下痛点: SQL性能问题频发:约65%的性能问题源于低效SQL,但开发人员缺乏专业的优化指导 知识传承困难:复杂的业务SQL往往缺乏文档说明,新成员理解成本高 错误诊断耗时:平均每个SQL错误需要45分钟排查,严重影响开发效率 这些痛点导致企业SQL开发效率低下,平均每个开发人员每天要花费2-3小时在SQL相关问题的处理上。 基于以上问题,袋鼠云实时开发平台引入了智能AI,深度集成于企业数据库开发环境,提供从SQL编写、优化到运维的全生命周期智能支持。助力企业开发效率提升40%+、性能问题减少60%、有效缩短新人上手时间。 重点功能介绍 智能优化 平台提供SQL语句性能优化建议,并支持一键替换原SQL。 智能注释 对SQL语句添加注释,方便理解表的作用及SQL的作用。其中注释内容包含:各数据表的业务含义、SQL的总体功能描述、关键操作步骤说明。 智能解释 平台支持解析选中SQL的业务逻辑。其中解释内容包括:SQL的预期输出结果、各关键操作步骤的业务含义、涉及数据表的关系说明。 日志解析 解析SQL执行错误并提供解决方案。其中支持场景包含:语法错误定位与修正建议、运行时错误原因分析、资源不足等系统级问题诊断。 灵瞳(AI助手)对话 元数据智能检索 支持查询平台存储的元数据信息 可展示表结构、字段说明等详细信息 示例查询:"显示customer表的所有字段" ​ 知识库智能应答 自动匹配知识库中的解决方案 支持多轮对话上下文理解 知识来源:历史问题库、最佳实践文档 SQL智能补全 该功能可以基于当前编辑的SQL上下文提供建议,支持表名、字段名、函数名等智能补全,学习用户习惯提供个性化建议。 典型应用场景 场景一:金融行业复杂报表开发 用户痛点:某银行数据分析团队需要每日生成20+张监管报表,SQL平均长度超过500行,维护困难。 平台解决方案: 使用智能注释自动生成报表SQL的业务说明 通过执行计划分析识别全表扫描等性能问题 新人通过"解释"功能快速理解复杂业务逻辑 场景二:某公司数据仓库建设 用户痛点:某公司建设企业数据仓库,涉及300+数据表的关联查询。 平台解决方案: 通过元数据检索快速查找所需表结构 字段智能补全避免记忆大量表名 袋鼠云实时开发平台通过深度集成AI能力,致力于解决SQL开发中的核心痛点,平台提供从编写、注释、解释到诊断的全流程智能支持,显著提升开发效率,降低维护成本。袋鼠云始终坚持帮助企业数据团队释放潜能,让数据价值高效兑现! ​

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册