首页 文章 精选 留言 我的

精选列表

搜索[官方镜像],共10000篇文章
优秀的个人博客,低调大师

PostgreSQL官方并行更新时间表

2013年10月,创建了两个主要的基础架构:Dynamic Background Workers 和 Dynamic Shared Memory。 2014年11月,Amit Kapila发布了并行顺序扫描(parallel sequential scan)的草案补丁,Robert Haas 发布了并行模式和并行上下文的(parallel mode and parallel contexts)草案补丁以及引入了一个名为pg_background的contrib模块的补丁。 2016年1月,并行基础架构被提交到PostgreSQL 9.5。 2016年3月,并行顺序扫描被提交到PostgreSQL 9.6。支持 并行连接 和 并行聚合(parallel joins and parallel aggregation),并于4月发布。 2017

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

Android--BRVAH官方使用指南

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/80498446 BRVAH是一个强大的RecyclerAdapter框架(什么是RecyclerView?),它能节约开发者大量的开发时间,集成了大部分列表常用需求解决方案。为什么会有它?请查看「Android开源框架BRVAH由来篇」该框架于2016年4月10号发布的第1个版本到现在已经一年多了,经历了800多次代码提交,140多次版本打包,修复了1000多个问题,获得了9000多star,非常感谢大家的使用以及反馈。本篇为BRVAH的使用指南以及包含常见问题会第一时间更新最新的使用方法。最新版本请查看releases,由于持续更新。 文章目录 框架引入 优化Adapter代码和原始的adapter相对,减少70%的代码量。 添加Item事件Item的点击事件Item的长按事件Item子控件的点击事件Item子控件的长按事件 添加列表加载动画一行代码轻松切换5种默认动画。 添加头部、尾部一行代码搞定,感觉又回到ListView时代。 自动加载上拉加载无需监听滑动事件,可自定义加载布局,显示异常提示,自定义异常提示。同时支持下拉加载。 分组布局随心定义分组头部。 多布局简单配置、无需重写额外方法。 设置空布局比Listview的setEmptyView还要好用。 添加拖拽、滑动删除开启,监听即可,就是这么简单。 树形列表比ExpandableListView还要强大,支持多级。 自定义ViewHolder支持自定义ViewHolder,让开发者随心所欲。 扩展框架组合第三方框架,轻松实现更多需求定制。 框架引入 先在 build.gradle(Project:XXXX) 的 repositories 添加: allprojects { repositories { ... maven { url "https://jitpack.io" } } } 然后在 build.gradle(Module:app) 的 dependencies 添加: dependencies { compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30' } 更新说明:https://github.com/CymChad/BaseRecyclerViewAdapterHelper/releases 注意 版本:2.9.28Change the method setVisible --> setGoneThe new method setVisible setVisible:Set a view visibility to VISIBLE (true) or INVISIBLE (false).setGone: Set a view visibility to VISIBLE (true) or GONE (false). 注意: 一旦出现加载失败的情况,只有两种情况: 配置没配置好配置没配置好,有几种情况:1. 只配置了dependencies2. 配置repositories,但是位置错了,build.gradle(Project:XXXX) 文件下的repositories有两个,一个是buildscript下面的,一个是allprojects下面的,要配置到allprojects下面才是对的。3. 版本号前面多一个v,这个是我的锅,在2.1.2版本之前都是带v的,之后(包含2.1.2)都不需要带v。 网络原因(这个就不解释了) 使用Adapter 和原始的adapter相对,减少70%的代码量。 使用代码 public class HomeAdapter extends BaseQuickAdapter<HomeItem, BaseViewHolder> { public HomeAdapter(int layoutResId, List data) { super(layoutResId, data); } @Override protected void convert(BaseViewHolder helper, HomeItem item) { helper.setText(R.id.text, item.getTitle()); helper.setImageResource(R.id.icon, item.getImageResource()); // 加载网络图片 Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) helper.getView(R.id.iv)); } } 注:如果想深入了解的原理可以查看RecyclerView.Adapter优化了吗? 使用 首先需要继承BaseQuickAdapter,然后BaseQuickAdapter<Status, BaseViewHolder>第一个泛型Status是数据实体类型,第二个BaseViewHolder是ViewHolder其目的是为了支持扩展ViewHolder。 赋值 可以直接使用viewHolder对象点相关方法通过传入viewId和数据进行,方法支持链式调用。如果是加载网络图片或自定义view可以通过viewHolder.getView(viewId)获取该控件。 常用方法 viewHolder.getLayoutPosition() 获取当前item的position 常见问题 这些问题不是使用该库的问题,但是经常有人问这些问题,所以特意写出来,帮助后续遇到以下问题的开发者们。 为什么有数据不显示?请检查一下你的RecyclerView是否设置了LayoutManager。 为什么有10条数据,只显示1条?请检查一下item的布局最外层的Layout是不是layout_height设置了match_parent. 数据状态错乱这个问题无论是RecyclerView还是ListView不做处理都会出现问题,这个本质上是由于布局重用机制导致的,解决办法是通过数据状态来控制控件的状态,一定要设置状态无论什么状态,if和else是少不了的,如下代码: if(entity.isCheck){ checkBox.isChecked(true); } else { checkBox.isChecked(false); } 解决缓存问题案例: 处理文本框和单选的缓存问题 实现三级伸缩,并解决多选框的复用 添加Item事件 Item的点击事件 adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { Log.d(TAG, "onItemClick: "); Toast.makeText(ItemClickActivity.this, "onItemClick" + position, Toast.LENGTH_SHORT).show(); } }); Item的长按事件 adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() { @Override public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) { Log.d(TAG, "onItemLongClick: "); Toast.makeText(ItemClickActivity.this, "onItemLongClick" + position, Toast.LENGTH_SHORT).show(); return false; } }); 注意:嵌套recycleView的情况下需要使用你使用 adapter. setOnItemClickListener 来设置点击事件,如果使用recycleView.addOnItemTouchListener会累计添加的。 Item子控件的点击事件首先在adapter的convert方法里面通过viewHolder.addOnClickListener绑定一下的控件id @Override protected void convert(BaseViewHolder viewHolder, Status item) { viewHolder.setText(R.id.tweetName, item.getUserName()) .setText(R.id.tweetText, item.getText()) .setText(R.id.tweetDate, item.getCreatedAt()) .setVisible(R.id.tweetRT, item.isRetweet()) .addOnClickListener(R.id.tweetAvatar) .addOnClickListener(R.id.tweetName) .linkify(R.id.tweetText); } 然后在设置 adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() { @Override public boolean onItemChildClick(BaseQuickAdapter adapter, View view, int position) { Log.d(TAG, "onItemChildClick: "); Toast.makeText(ItemClickActivity.this, "onItemChildClick" + position, Toast.LENGTH_SHORT).show(); return false; } }); Item子控件的长按事件步骤同上使用方法不同。adapter中绑定方法将addOnClickListener改成addOnLongClickListener.设置点击事件方法setOnItemChildClickListener改成setOnItemChildLongClickListener 注意:设置子控件的事件,如果不在adapter中绑定,点击事件无法生效,因为无法找到你需要设置的控件。 如果需要在点击事件中获取其他子控件可以使用: getViewByPosition(RecyclerView recyclerView, int position, @IdRes int viewId) 注意:如果有header的话需要处理一下position加上 headerlayoutcount。 添加列表加载动画 开启动画(默认为渐显效果)adapter.openLoadAnimation();默认提供5种方法(渐显、缩放、从下到上,从左到右、从右到左) public static final int ALPHAIN = 0x00000001; /** * Use with {@link #openLoadAnimation} */ public static final int SCALEIN = 0x00000002; /** * Use with {@link #openLoadAnimation} */ public static final int SLIDEIN_BOTTOM = 0x00000003; /** * Use with {@link #openLoadAnimation} */ public static final int SLIDEIN_LEFT = 0x00000004; /** * Use with {@link #openLoadAnimation} */ public static final int SLIDEIN_RIGHT = 0x00000005; 切换动画 quickAdapter.openLoadAnimation(BaseQuickAdapter.ALPHAIN); 自定义动画 quickAdapter.openLoadAnimation(new BaseAnimation() { @Override public Animator[] getAnimators(View view) { return new Animator[]{ ObjectAnimator.ofFloat(view, "scaleY", 1, 1.1f, 1), ObjectAnimator.ofFloat(view, "scaleX", 1, 1.1f, 1) }; } }); 动画默认只执行一次,如果想重复执行可设置 mQuickAdapter.isFirstOnly(false); 注:如果想深入了解的原理可以查看BaseRecyclerAdapter之添加动画(策略模式) 设置不显示动画数量 adapter.setNotDoAnimationCount(count); 首次到界面的item都依次执行加载动画 由于进入界面的item都是很多的速度进来的所以不会出现滑动显示的依次执行动画效果,这个时候会一起执行动画,如果觉得这样的效果不好可以使用setNotDoAnimationCount设置第一屏item不执行动画,但是如果需要依次执行动画可以重写startAnim让第一个屏幕的item动画延迟执行即可。 @Override protected void startAnim(Animator anim, int index) { super.startAnim(anim, index); if (index < count) anim.setStartDelay(index * 150); } 添加头部、尾部 添加 mQuickAdapter.addHeaderView(getView()); mQuickAdapter.addFooterView(getView()); 注:如果想深入了解的原理可以查看BaseRecyclerAdapter之添加不同布局(头部尾部) 删除指定view mQuickAdapter.removeHeaderView(getView); mQuickAdapter.removeFooterView(getView); 删除所有 mQuickAdapter.removeAllHeaderView(); mQuickAdapter.removeAllFooterView(); 默认出现了头部就不会显示Empty,和尾部,配置以下方法也支持同时显示: setHeaderAndEmpty setHeaderFooterEmpty 默认头部尾部都是占满一行,如果需要不占满可以配置: setHeaderViewAsFlow setFooterViewAsFlow 自动加载 上拉加载 // 滑动最后一个Item的时候回调onLoadMoreRequested方法 setOnLoadMoreListener(RequestLoadMoreListener); 默认第一次加载会进入回调,如果不需要可以配置: mQuickAdapter.disableLoadMoreIfNotFullPage(); 回调处理代码 mQuickAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() { @Override public void onLoadMoreRequested() { mRecyclerView.postDelayed(new Runnable() { @Override public void run() { if (mCurrentCounter >= TOTAL_COUNTER) { //数据全部加载完毕 mQuickAdapter.loadMoreEnd(); } else { if (isErr) { //成功获取更多数据 mQuickAdapter.addData(DataServer.getSampleData(PAGE_SIZE)); mCurrentCounter = mQuickAdapter.getData().size(); mQuickAdapter.loadMoreComplete(); } else { //获取更多数据失败 isErr = true; Toast.makeText(PullToRefreshUseActivity.this, R.string.network_err, Toast.LENGTH_LONG).show(); mQuickAdapter.loadMoreFail(); } } } }, delayMillis); } }, mReyclerView); 加载完成(注意不是加载结束,而是本次数据加载结束并且还有下页数据) mQuickAdapter.loadMoreComplete(); 加载失败 mQuickAdapter.loadMoreFail(); 加载结束 mQuickAdapter.loadMoreEnd(); 注意:如果上拉结束后,下拉刷新需要再次开启上拉监听,需要使用setNewData方法填充数据。 打开或关闭加载(一般用于下拉的时候做处理,因为上拉下拉不能同时操作) mQuickAdapter.setEnableLoadMore(boolean); 预加载 // 当列表滑动到倒数第N个Item的时候(默认是1)回调onLoadMoreRequested方法 mQuickAdapter.setPreLoadNumber(int); 设置自定义加载布局 mQuickAdapter.setLoadMoreView(new CustomLoadMoreView()); public final class CustomLoadMoreView extends LoadMoreView { @Override public int getLayoutId() { return R.layout.view_load_more; } /** * 如果返回true,数据全部加载完毕后会隐藏加载更多 * 如果返回false,数据全部加载完毕后会显示getLoadEndViewId()布局 */ @Override public boolean isLoadEndGone() { return true; } @Override protected int getLoadingViewId() { return R.id.load_more_loading_view; } @Override protected int getLoadFailViewId() { return R.id.load_more_load_fail_view; } /** * isLoadEndGone()为true,可以返回0 * isLoadEndGone()为false,不能返回0 */ @Override protected int getLoadEndViewId() { return 0; } } <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/dp_40"> <LinearLayout android:id="@+id/load_more_loading_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="horizontal"> <ProgressBar android:id="@+id/loading_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleSmall" android:layout_marginRight="@dimen/dp_4" android:indeterminateDrawable="@drawable/sample_footer_loading_progress"/> <TextView android:id="@+id/loading_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/dp_4" android:text="@string/loading" android:textColor="#0dddb8" android:textSize="@dimen/sp_14"/> </LinearLayout> <FrameLayout android:id="@+id/load_more_load_fail_view" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"> <TextView android:id="@+id/tv_prompt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textColor="#0dddb8" android:text="@string/load_failed"/> </FrameLayout> </FrameLayout> 下拉加载(符合聊天软件下拉历史数据需求) 设置开启开关 mAdapter.setUpFetchEnable(true); 设置监听 mAdapter.setUpFetchListener(new BaseQuickAdapter.UpFetchListener() { @Override public void onUpFetch() { startUpFetch(); } }); private void startUpFetch() { count++; /** * set fetching on when start network request. */ mAdapter.setUpFetching(true); /** * get data from internet. */ mRecyclerView.postDelayed(new Runnable() { @Override public void run() { mAdapter.addData(0, genData()); /** * set fetching off when network request ends. */ mAdapter.setUpFetching(false); /** * set fetch enable false when you don't need anymore. */ if (count > 5) { mAdapter.setUpFetchEnable(false); } } }, 300); } 开始加载的位置 mAdapter.setStartUpFetchPosition(2); 分组布局 实体类必须继承SectionEntity public class MySection extends SectionEntity<Video> { private boolean isMore; public MySection(boolean isHeader, String header) { super(isHeader, header); } public MySection(Video t) { super(t); } } adapter构造需要传入两个布局id,第一个是item的,第二个是head的,在convert方法里面加载item数据,在convertHead方法里面加载head数据 public class SectionAdapter extends BaseSectionQuickAdapter<MySection> { public SectionAdapter(int layoutResId, int sectionHeadResId, List data) { super(layoutResId, sectionHeadResId, data); } @Override protected void convert(BaseViewHolder helper, MySection item) { helper.setImageUrl(R.id.iv, (String) item.t); } @Override protected void convertHead(BaseViewHolder helper,final MySection item) { helper.setText(R.id.header, item.header); helper.setOnClickListener(R.id.more, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context,item.header+"more..",Toast.LENGTH_LONG).show(); } }); } 多布局 实体类必须实现MultiItemEntity,在设置数据的时候,需要给每一个数据设置itemType public class MultipleItem implements MultiItemEntity { public static final int TEXT = 1; public static final int IMG = 2; private int itemType; public MultipleItem(int itemType) { this.itemType = itemType; } @Override public int getItemType() { return itemType; } } 在构造里面addItemType绑定type和layout的关系 public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem, BaseViewHolder> { public MultipleItemQuickAdapter(List data) { super(data); addItemType(MultipleItem.TEXT, R.layout.text_view); addItemType(MultipleItem.IMG, R.layout.image_view); } @Override protected void convert(BaseViewHolder helper, MultipleItem item) { switch (helper.getItemViewType()) { case MultipleItem.TEXT: helper.setImageUrl(R.id.tv, item.getContent()); break; case MultipleItem.IMG: helper.setImageUrl(R.id.iv, item.getContent()); break; } } } 注:如果想深入了解的原理可以查看BaseRecyclerAdapter之添加不同布局(优化篇) 如果考虑到在GridLayoutManager复用item问题可以配置: multipleItemAdapter.setSpanSizeLookup(new BaseQuickAdapter.SpanSizeLookup() { @Override public int getSpanSize(GridLayoutManager gridLayoutManager, int position) { return data.get(position).getSpanSize(); } }); 如果大家觉得这种多布局方式有点由于耦合了实体类,还有支持另外一种多布局方式具体可查看更便捷的多布局, 为 BaseQuickAdapter 设置代理. 如果使用多布局出现这个NotFoundException异常,有可能是addItemType()两个参数写反了。 设置空布局 // 没有数据的时候默认显示该布局 mQuickAdapter.setEmptyView(getView()); 如果用网格布局的话,设置空布局就不能给全屏,可以使用瀑布流布局。 添加拖拽、滑动删除 拖拽和滑动删除的回调方法 OnItemDragListener onItemDragListener = new OnItemDragListener() { @Override public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos){} @Override public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {} @Override public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {} } OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() { @Override public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {} @Override public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {} @Override public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {} }; adapter需要继承BaseItemDraggableAdapter public class ItemDragAdapter extends BaseItemDraggableAdapter<String, BaseViewHolder> { public ItemDragAdapter(List data) { super(R.layout.item_draggable_view, data); } @Override protected void convert(BaseViewHolder helper, String item) { helper.setText(R.id.tv, item); } } Activity使用代码 mAdapter = new ItemDragAdapter(mData); ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter); ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback); itemTouchHelper.attachToRecyclerView(mRecyclerView); // 开启拖拽 mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true); mAdapter.setOnItemDragListener(onItemDragListener); // 开启滑动删除 mAdapter.enableSwipeItem(); mAdapter.setOnItemSwipeListener(onItemSwipeListener); 默认不支持多个不同的 ViewType 之间进行拖拽,如果开发者有所需求: 重写ItemDragAndSwipeCallback里的onMove()方法,return true即可 树形列表 例子:三级菜单 // if you don't want to extent a class, you can also use the interface IExpandable. // AbstractExpandableItem is just a helper class. public class Level0Item extends AbstractExpandableItem<Level1Item> {...} public class Level1Item extends AbstractExpandableItem<Person> {...} public class Person {...} adapter需要继承BaseMultiItemQuickAdapter public class ExpandableItemAdapter extends BaseMultiItemQuickAdapter<MultiItemEntity, BaseViewHolder> { public ExpandableItemAdapter(List<MultiItemEntity> data) { super(data); addItemType(TYPE_LEVEL_0, R.layout.item_expandable_lv0); addItemType(TYPE_LEVEL_1, R.layout.item_expandable_lv1); addItemType(TYPE_PERSON, R.layout.item_text_view); } @Override protected void convert(final BaseViewHolder holder, final MultiItemEntity item) { switch (holder.getItemViewType()) { case TYPE_LEVEL_0: .... //set view content holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = holder.getAdapterPosition(); if (lv0.isExpanded()) { collapse(pos); } else { expand(pos); } }}); break; case TYPE_LEVEL_1: // similar with level 0 break; case TYPE_PERSON: //just set the content break; } } 开启所有菜单: adapter.expandAll(); 删除某一个item(添加和修改的思路是一样的) // 获取当前父级位置 int cp = getParentPosition(person); // 通过父级位置找到当前list,删除指定下级 ((Level1Item)getData().get(cp)).removeSubItem(person); // 列表层删除相关位置的数据 getData().remove(holder.getLayoutPosition()); // 更新视图 notifyDataSetChanged(); 自定义ViewHolder 需要继承BaseViewHolder public class MovieViewHolder extends BaseViewHolder 然后修改adapter的第二个泛型为自定义的ViewHolder public class DataBindingUseAdapter extends BaseQuickAdapter<Movie, DataBindingUseAdapter.MovieViewHolder> 注意:需要单独建一个外部类继承BaseViewHolder,否则部分机型会出现ClassCastException,如果是内部类的构造方法要是public,定义的那个类也最好是public。 混淆 -keep class com.chad.library.adapter.** { *; } -keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter -keep public class * extends com.chad.library.adapter.base.BaseViewHolder -keepclassmembers class **$** extends com.chad.library.adapter.base.BaseViewHolder { <init>(...); } 扩展框架 由于adapter本身能力有限,我们又不想耦合view层所以有些需求是现实不了,于是合作了一些优秀开源库,为开发者提供更多可能性。以下扩展框架都是有结合BRVAH的demo。 PinnedSectionItemDecoration:一个强大的粘性标签库 EasyRefreshLayout:这个库让你轻松实现下拉刷新和上拉更多 EasySwipeMenuLayout:独立的侧滑删除 本文章由于持续更新,建议点赞收藏,便于查看。也欢迎大家提出更多建议,我就会第一时间看到后回复,持续到什么时候?只要还没去领盒饭,我就会持续,

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

官方简要解读 LinuxKit 工具包

LinuxKit 是什么? LinuxKit所包含的工具可以允许用户构建自定义的Linux子系统。所有的系统服务都是可以替换的容器,并且用户可以移除所有不需要的东西。这是一款非常符合Docker设计理念的工具,所有默认的组件都可以替换成与用户需求相匹配的组件。 安全性是 Docker 最重要的目标 LinuxKit是一个开源项目,Docker称之所以将其构建在容器中,是为了构建一个安全、精简和可移植的操作系统。 Docker 公司称安全性是其重要等级的目标,该目标与 NIST(美国国家标准技术研究所)在其《Application Container Security Guide》草案中的声明是一致的,即“使用特定的容器操作系统而不是通用的操作系统来减少攻击平面。当使用特定的容器操作系统时,其可攻击平面通常远小于通用操作系统,因此攻击和危害

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

android之官方下拉刷新组件SwipeRefreshLayout

一、问题描述 在android开发中,使用最多的数据刷新方式就是下拉刷新了,而完成此功能我们使用最多的就是第三方的开源库PullToRefresh。现如今,google也忍不住推出了自己的下拉组件SwipeRefreshLayout,下面我们通过api文档和源码来分析学习如何使用SwipeRefreshLayout。 先看效果图: 二、SwipeRefreshLayout的具体用法 下面我们来看SwipeRefreshLayout的具体用法,顾名思义此组件就是一个布局,只不过要注意的是此布局内只能有一个直接子View。其实通过文档我们可以知道SwipeRefreshLayout只不过是继承了ViewGroup。 查看文档,我们可以知道,在SwipRefreshLayout中存在一个接口,通过此接口我们可以监听滑动手势,其实使用此组件最重要的步骤就是实现此接口的onRefresh方法,在此方法中实现数据的更新操作。如下: 接口中的方法: 除了OnRefreshListener接口外,SwipRefreshLayout中还有一些其他重要的方法,具体如下: 1、setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener):设置手势滑动监听器。 2、setProgressBackgroundColor(int colorRes):设置进度圈的背景色。 3、setColorSchemeResources(int… colorResIds):设置进度动画的颜色。 4、setRefreshing(Boolean refreshing):设置组件的刷洗状态。 5、setSize(int size):设置进度圈的大小,只有两个值:DEFAULT、LARGE 弄清楚API后,我们下面进行实际编码,首先先做布局,具体内容如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version= "1.0" encoding= "utf-8" ?> <android.support.v4.widget.SwipeRefreshLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:layout_width= "match_parent" android:layout_height= "match_parent" android:orientation= "vertical" android:id= "@+id/swipeLayout" > <ListView android:id= "@+id/mylist" android:layout_width= "match_parent" android:layout_height= "wrap_content" /> </android.support.v4.widget.SwipeRefreshLayout> Activity核心代码如下: 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 swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipeLayout); swipeRefreshLayout.setColorSchemeResources(R.color.swipe_color_1, R.color.swipe_color_2, R.color.swipe_color_3, R.color.swipe_color_4); swipeRefreshLayout.setSize(SwipeRefreshLayout.LARGE);; swipeRefreshLayout.setProgressBackgroundColor(R.color.swipe_background_color); //swipeRefreshLayout.setPadding(20, 20, 20, 20); //swipeRefreshLayout.setProgressViewOffset(true, 100, 200); //swipeRefreshLayout.setDistanceToTriggerSync(50); swipeRefreshLayout.setProgressViewEndTarget( true , 100 ); swipeRefreshLayout.setOnRefreshListener( new OnRefreshListener() { @Override public void onRefresh() { new Thread( new Runnable() { @Override public void run() { data.clear(); for ( int i= 0 ;i< 20 ;i++){ data.add( "SwipeRefreshLayout下拉刷新" +i); } try { Thread.sleep( 5000 ); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.sendEmptyMessage( 1 ); } }).start(); } }); //handler private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super .handleMessage(msg); switch (msg.what) { case 1 : swipeRefreshLayout.setRefreshing( false ); adapter.notifyDataSetChanged(); //swipeRefreshLayout.setEnabled(false); break ; default : break ; } } }; 通过如上步骤,我们就实现了一个简单的下拉刷新操作,在此基础上,我们可以分析研究一下SwipeRefreshLayout是如何实现的。 通过源码我们发现SwipeRefreshLayout中的两个重要的属性: private MaterialProgressDrawable mProgress; private CircleImageView mCircleView; 这两个属性正是用于实现进度动画效果的,在方法createProgressView中,我们看到mCircleView最终加入到了SwipeRefreshLayout中。 1 2 3 4 5 6 7 8 private void createProgressView() { mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/ 2 ); mProgress = new MaterialProgressDrawable(getContext(), this ); mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); mCircleView.setImageDrawable(mProgress); mCircleView.setVisibility(View.GONE); addView(mCircleView); } 同时我们也可以查看到CirlceImageView继承了ImageView,MaterialProgressDrawabel继承了Drawable,至此我们也就明白了下来进度动画是如何实现的了,具体的细节在不做过多赘述,可自行查看源码。 class CircleImageView extends ImageView class MaterialProgressDrawable extends Drawable implements Animatable 具体的下拉功能实现主要是在onInterceptTouchEvent、onTouchEvent方法中完成的,在此我就不在将具体代码贴出来了,大家可自行查看。 想要进一步了解的同学,可以下载完整工程,自行运行查看!有问题也可以跟帖讨论哦~ 本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/5378851.html,如需转载请自行联系原作者

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

谷歌官方Android应用架构库——LiveData

LiveData 是一个数据持有者类,它持有一个值并允许观察该值。不同于普通的可观察者,LiveData 遵守应用程序组件的生命周期,以便 Observer 可以指定一个其应该遵守的 Lifecycle。 如果 Observer 的 Lifecycle 处于 STARTED 或 RESUMED 状态,LiveData 会认为 Observer 处于活动状态。 publicclassLocationLiveDataextendsLiveData<Location>{ privateLocationManagerlocationManager; privateSimpleLocationListenerlistener=newSimpleLocationListener(){ @Override publicvoidonLocationChanged(Locationlocation){ setValue(location); } }; publicLocationLiveData(Contextcontext){ locationManager=(LocationManager)context.getSystemService( Context.LOCATION_SERVICE); } @Override protectedvoidonActive(){ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,0,0,listener); } @Override protectedvoidonInactive(){ locationManager.removeUpdates(listener); } } Location 监听的实现有 3 个重要部分: onActive():当 LiveData 有一个处于活动状态的观察者时该方法被调用,这意味着需要开始从设备观察位置更新。 vonInactive():当 LiveData 没有任何处于活动状态的观察者时该方法被调用。由于没有观察者在监听,所以没有理由保持与 LocationManager 的连接。这是非常重要的,因为保持连接会显著消耗电量并且没有任何好处。 setValue():调用该方法更新 LiveData 实例的值,并将此变更通知给处于活动状态的观察者。 可以像下面这样使用新的 LocationLiveData: publicclassMyFragmentextendsLifecycleFragment{ publicvoidonActivityCreated(BundlesavedInstanceState){ LiveData<Location>myLocationListener=...; Util.checkUserStatus(result->{ if(result){ myLocationListener.addObserver(this,location->{ //updateUI }); } }); } } 请注意,addObserver() 方法将 LifecycleOwner 作为第一个参数传递。这样做表示该观察者应该绑定到 Lifecycle,意思是: 如果 Lifecycle 不处于活动状态(STARTED 或 RESUMED),即使该值发生变化也不会调用观察者。 如果 Lifecycle 被销毁,那么自动移除观察者。 LiveData 是生命周期感知的事实给我们提供了一个新的可能:可以在多个 activity,fragment 等之间共享它。为了保持实例简单,可以将其作为单例,如下所示: publicclassLocationLiveDataextendsLiveData<Location>{ privatestaticLocationLiveDatasInstance; privateLocationManagerlocationManager; @MainThread publicstaticLocationLiveDataget(Contextcontext){ if(sInstance==null){ sInstance=newLocationLiveData(context.getApplicationContext()); } returnsInstance; } privateSimpleLocationListenerlistener=newSimpleLocationListener(){ @Override publicvoidonLocationChanged(Locationlocation){ setValue(location); } }; privateLocationLiveData(Contextcontext){ locationManager=(LocationManager)context.getSystemService( Context.LOCATION_SERVICE); } @Override protectedvoidonActive(){ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,0,0,listener); } @Override protectedvoidonInactive(){ locationManager.removeUpdates(listener); } } 现在 fragment 可以像下面这样使用它: publicclassMyFragmentextendsLifecycleFragment{ publicvoidonActivityCreated(BundlesavedInstanceState){ Util.checkUserStatus(result->{ if(result){ LocationLiveData.get(getActivity()).observe(this,location->{ //updateUI }); } }); } } 可能会有多个 fragment 和 activity 在观察 MyLocationListener 实例,LiveData 可以规范的管理它们,以便只有当它们中的任何一个可见(即处于活动状态)时才连接到系统服务。 LiveData 有以下优点: 没有内存泄漏:因为 Observer 被绑定到它们自己的 Lifecycle 对象上,所以,当它们的 Lifecycle 被销毁时,它们能自动的被清理。 不会因为 activity 停止而崩溃:如果 Observer 的 Lifecycle 处于闲置状态(例如:activity 在后台时),它们不会收到变更事件。 始终保持数据最新:如果 Lifecycle 重新启动(例如:activity 从后台返回到启动状态)将会收到最新的位置数据(除非还没有)。 正确处理配置更改:如果 activity 或 fragment 由于配置更改(如:设备旋转)重新创建,将会立即收到最新的有效位置数据。 资源共享:可以只保留一个 MyLocationListener 实例,只连接系统服务一次,并且能够正确的支持应用程序中的所有观察者。 不再手动管理生命周期:fragment 只是在需要的时候观察数据,不用担心被停止或者在停止之后启动观察。由于 fragment 在观察数据时提供了其 Lifecycle,所以 LiveData 会自动管理这一切。 LiveData 的转换 有时候可能会需要在将 LiveData 发送到观察者之前改变它的值,或者需要更具另一个 LiveData 返回一个不同的 LiveData 实例。 Lifecycle 包提供了一个 Transformations 类包含对这些操作的帮助方法。 ,%20android.arch.core.util.Function LiveData<User>userLiveData=...; LiveData<String>userName=Transformations.map(userLiveData,user->{ user.name+""+user.lastName }); ,%20android.arch.core.util.Function privateLiveData<User>getUser(Stringid){ ...; } LiveData<String>userId=...; LiveData<User>user=Transformations.switchMap(userId,id->getUser(id)); 使用这些转换允许在整个调用链中携带观察者的 Lifecycle 信息,以便只有在观察者观察到 LiveData 的返回时才运算这些转换。转换的这种惰性运算性质允许隐式的传递生命周期相关行为,而不必添加显式的调用或依赖。 每当你认为在 ViewModel 中需要一个 Lifecycle 类时,转换可能是解决方案。 例如:假设有一个 UI,用户输入一个地址然后会收到该地址的邮政编码。该 UI 简单的 ViewModel 可能像这样: classMyViewModelextendsViewModel{ privatefinalPostalCodeRepositoryrepository; publicMyViewModel(PostalCodeRepositoryrepository){ this.repository=repository; } privateLiveData<String>getPostalCode(Stringaddress){ //DON'TDOTHIS returnrepository.getPostCode(address); } } 如果是像这种实现,UI 需要先从之前的 LiveData 注销并且在每次调用 getPostalCode() 时重新注册到新的实例。此外,如果 UI 被重新创建,它将会触发新的 repository.getPostCode() 调用,而不是使用之前的调用结果。 不能使用那种方式,而应该实现将地址输入转换为邮政编码信息。 classMyViewModelextendsViewModel{ privatefinalPostalCodeRepositoryrepository; privatefinalMutableLiveData<String>addressInput=newMutableLiveData(); publicfinalLiveData<String>postalCode= Transformations.switchMap(addressInput,(address)->{ returnrepository.getPostCode(address); }); publicMyViewModel(PostalCodeRepositoryrepository){ this.repository=repository } privatevoidsetInput(Stringaddress){ addressInput.setValue(address); } } 请注意,我们甚至使 postalCode 字段为 public final,因为它永远不会改变。postalCode 被定义为 addressInput 的转换,所以当 addressInput 改变时,如果有处于活动状态的观察者,repository.getPostCode() 将会被调用。如果在调用时没有处于活动状态的观察者,在添加观察者之前不会进行任何运算。 该机制允许以较少的资源根据需要惰性运算来创建 LiveData。ViewModel 可以轻松获取到 LiveData 并在它们上面定义转换规则。 创建新的转换 在应用程序中可能会用到十几种不同的特定转换,但是默认是不提供的。可以使用 MediatorLiveData 实现自己的转换,MediatorLiveData 是为了用来正确的监听其它 LiveData 实例并处理它们发出的事件而特别创建的。MediatorLiveData 需要特别注意正确的向源 LiveData 传递其处于活动/闲置状态。有关详细信息,请参阅 Transformations 类。 本文作者:佚名 来源:51CTO

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

官方文档,才是正途-docker-compose

需要的ingress网络映射,还是host宿主机端口映射: https://docs.docker.com/compose/compose-file/#secrets ======================== docker service create --name web \ --publish mode=host,published=80,target=80 \ nginx ========================== ports Expose ports. SHORT SYNTAX Either specify both ports (HOST:CONTAINER), or just the container port (a random host port will be chosen). Note: When mapping ports in theHOST:CONTAINERformat, you may experience erroneous results when using a container port lower than 60, because YAML will parse numbers in the formatxx:yyas sexagesimal (base 60). For this reason, we recommend always explicitly specifying your port mappings as strings. ports: - "3000" - "3000-3005" - "8000:8000" - "9090-9091:8080-8081" - "49100:22" - "127.0.0.1:8001:8001" - "127.0.0.1:5000-5010:5000-5010" - "6060:6060/udp" LONG SYNTAX The long form syntax allows the configuration of additional fields that can’t be expressed in the short form. target: the port inside the container published: the publicly exposed port protocol: the port protocol (tcporudp) mode:hostfor publishing a host port on each node, oringressfor a swarm mode port which will be load balanced. ports: - target: 80 published: 8080 protocol: tcp mode: host Note:The long syntax is new in v3.2

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

Apache Storm 官方文档 —— 源码组织结构

Strom 的代码有三个层次: 第一,Storm 在一开始就是按照兼容多语言的目的来设计的。Nimbus 是一个 Thrift 服务,拓扑也被定义为 Thrift 架构。Thrift 的使用使得 Storm 可以用于任何一种语言。 第二,所有的 Storm 接口都设计为 Java 接口。所以,尽管 Storm 核心代码中有大量的 Clojure 实现,所有的访问都必须经过 Java API。这就意味着 Storm 的每个特性都可以通过 Java 来实现。 第三,Storm 的实现中大量使用了 Clojure。可以说,Storm 的代码结构大概是一半的 Java 代码加上一半的 Clojure 代码。但是由于 Clojure 更具有表现力,所以实际上 Storm 的核心逻辑大多是采用 Clojure 来实现的。 下面详细说明了每个层次的细节信息。 storm.thrift 要理解 Storm 的代码架构,首先需要了解storm.thrift文件。 Storm 使用这个fork版本的 Thrift(“storm” 分支)来生成代码。这个 “fork” 版本实际上就是 Thrift7,其中所有的 Java package 也都重命名成了org.apache.thrift7。在其他方面,它与 Thrift7 完全相同。这个 fork 主要是为了解决 Thrift 缺乏向后兼容的机制的问题,同时,也可以让用户在自己的 Storm 拓扑中使用其他版本的 Thrift。 拓扑中的每个 spout 或者 bolt 都有一个特定的标识,这个标识称为“组件 id”。组件 id 主要为了从拓扑中 spout 和 bolt 的输出流中选择一个或多个流作为某个 bolt 订阅的输入流。Storm 拓扑中就包含有一个组件 id 与每种类型的组件(spout 与 bolt)相关联的 map。 Spout 和 Bolt 有相同的 Thrift 定义。我们来看看Bolt 的 Thrift 定义。它包含一个ComponentObject结构和一个ComponentCommon结构。 ComponentObject定义了 bolt 的实现,这个实现可以是以下三种类型中的一种: 一个 Java 序列化对象(实现了IBolt接口的对象)。 一个用于表明其他语言的实现的ShellComponent对象。以这种方式指定一个 bolt 会让 Storm 实例化一个ShellBolt对象来处理基于 JVM 的 worker 进程与组件的非 JVM 实现之间的通信。 一个带有类名与构造器参数的 Java 对象结构,Storm 可以使用这个结构来实例化 bolt。如果你需要定义一个非 JVM 语言的拓扑这个类型会很有用。使用这种方式,你可以在不创建并且序列化一个 Java 对象的情况下使用基于 JVM 的 spout 与 bolt。 ComponentCommon定义了组件的其他方面特性,包括: 该组件的输出流以及每个流的 metadata(无论是一个直接流还是基于域定义的流); 该组件消费的输入流(使用流分组所定义的一个将组件 id 与流 id 相关联的 map 来指定); 该组件的并行度; 该组件的组件级配置。 注意,spout 的结构也有一个ComponentCommon域,所以理论上说 spout 也可以声明一个输入流。然而 Storm 的 Java API 并没有为 spout 提供消费其他的流的方法,并且如果你为 spout 声明了输入流,在提交拓扑的时候也会报错。这是因为 spout 的输入流声明不是为了用户的使用,而是为了 Storm 内部的使用。Storm 会为拓扑添加隐含的流与 bolt 来设置应答框架(acking framework)。这些隐含的流中就有两个流用于从 acker bolt 向拓扑中的每个 spout 发送消息。在发现 tuple 树完成或者失败之后,acker 就会通过这些隐含的流发送 “ack” 或者 “fail” 消息。将用户的拓扑转化为运行时拓扑的代码在这里。 Java 接口 Storm 的对外接口基本上为 Java 接口,主要的几个接口有: IRichBolt IRichSpout TopologyBuilder 大部分接口的策略为: 使用一个 Java 接口来定义接口; 实现一个具有适当的默认实现的 Base 类。 你可以从BaseRichSpout类中观察到这种策略的工作机制。 如上所述,Spout 和 Bolt 都已经根据拓扑的 Thrift 定义进行了序列化。 在这些接口中,IBolt、ISpout与IRichBolt、IRichSpout之间存在着一些细微的差别。其中最主要的区别是带有 “Rich” 的接口中增加了declareOutputFields方法。这种区别的原因主要在于每个输出流的输出域声明必须是 Thrift 结构的一部分(这样才能实现跨语言操作),而用户本身只需要将流声明为自己的类的一部分即可。TopologyBuilder在构造 Thrift 结构时所做的就是调用declareOutputFields方法来获取声明并将其转化为 Thrift 结构。这种转化过程可以在TopologyBuilder的源码中看到。 实现 通过 Java 接口来详细说明所有的功能可以确保 Storm 的每个特征都是有效的。更重要的是,关注 Java 接口可以让有 Java 使用经验的用户更易上手。 另一方面,Storm 的核心架构主要是通过 Clojure 实现的。尽管按照一般的计数规则来说代码库中 Java 与 Clojure 各占 50%,但是大部分逻辑实现还是基于 Clojure 的。不过也有两个例外,分别是 DRPC 和事务型拓扑的实现。这两个部分是完全使用 Java 实现的。这是为了说明在 Storm 中如何实现高级抽象。DRPC 和事务型拓扑的实现分别位于backtype.storm.coordination、backtype.storm.drpc和backtype.storm.transactional包中。 以下是主要的 Java 包和 Clojure 命名空间的总结。 Java packages backtype.storm.coordination: 实现了用于将批处理整合到 Storm 上层的功能,DRPC 和事务型拓扑都需要这个功能。CoordinatedBolt是其中最重要的类。 backtype.storm.drpc: DRPC 高级抽象的实现。 backtype.storm.generated: 为 Storm 生成的 Thrift 代码(使用了这个fork版本的 Thrift,其中仅仅将包名重命名为 org.apache.thrift7 来避免与其他 Thrift 版本的冲突)。 backtype.storm.grouping: 包含自定义流分组的接口。 backtype.storm.hooks: 用于在 Storm 中添加事件钩子的接口,这些事件包括任务发送 tuple、tuple 被 ack 等等。 backtype.storm.serialization: Storm 序列化/反序列化 tuple 的接口。这是在Kryo的基础上构建的。 backtype.storm.spout: Spout 与一些关联接口的定义(例如SpoutOutputCollector)。其中也包含有用于实现非 JVM 语言 spout 的协议的ShellSpout。 backtype.storm.task: Bolt 与关联接口的定义(例如OutputCollector)。其中也包含有用于实现非 JVM 语言 bolt 的协议的ShellBolt。最后,TopologyContext也是在这里定义的,该类可以用于在拓扑运行时为 spout 和 bolt 提供拓扑以及他们自身执行的相关信息。 backtype.storm.testing: 包含很多 bolt 测试类以及用于 Storm 单元测试的工具类。 backtype.storm.topology: 在 Thrift 结构上层的 Java 层,用于为 Storm 提供完全的 Java API(用户不必了解 Thrift)。TopologyBuilder和一些为不同的 spout 和 bolt 提供帮助的基础类都在这里。稍微高级一点的IBasicBolt接口也在这里,该接口是一种实现基本的 bolt 的简单方式。 backtype.storm.transactional: 事务型拓扑的实现。 backtype.storm.tuple: Storm tuple 数据模型的实现。 backtype.storm.utils: 整个代码库中通用的数据结构和各种工具类。 Clojure namespaces 译者注:Clojure 部分内容暂不提供翻译。 转载自并发编程网 - ifeve.com

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

Apache Storm 官方文档 —— 问题与解决

本文介绍了用户在使用 Storm 过程中遇到的问题与相应的解决方法。 Worker 进程在启动时挂掉而没有留下堆栈跟踪信息的问题 可能出现的现象: 拓扑在一个节点上运行正常,但是多个 worker 进程在多个节点上就会崩溃 解决方案: 你的网络配置可能有问题,导致每个节点无法根据 hostname 连接到其他的节点。ZeroMQ 有时会在不能识别 host 的时候挂掉 进程。如果是这种情况,有两种可行的解决方案: 在 /etc/hosts 文件中配置好 hostname 与 IP 的对应关系 设置一个局域网 DNS 服务器,使得节点可以根据 hostname 定位到其他节点 节点之间无法通信 可能出现的现象: 每个 spout tuple 的处理都不成功 拓扑中的处理过程不起作用 解决方案: Storm 不支持 ipv6,你可以在 supervisor 的 child-opts 配置中添加-Djava.net.preferIPv4Stack=true参数,然后重启 supervisor。 你的网络配置可能存在问题,请参考上个问题中的解决方案。 拓扑在一段时间后停止了 tuple 的处理过程 可能出现的现象: 拓扑正常运行一段时间后突然停止了数据处理过程,并且 spout 的 tuple 一起开始处理失败 解决方案: 这是 ZeroMQ 2.1.10 中的一个已经确认的问题,请将 ZMQ 降级到 2.1.7 版本。 Storm UI 中没有显示出所有的 supervisor 信息 可能出现的现象: Storm UI 中缺少部分 supervisor 的信息 在刷新 Storm UI 页面后 supervisor 列表会变化 解决方案: 确保 supervisor 的本地工作目录是相互独立的(也就是说不要出现在 NFS 中共享同一个目录的情况) 尝试删除 supervisor 的本地工作目录,然后重启 supervisor 后台进程。supervisor 启动时会为自己创建一个唯一的 id 并存储在本地目录中。如果这个 id 被复制到其他节点中,就会让 Storm 无法确定哪个 supervisor 正在运行(这种情况并不少见,如果需要扩展集群,就很容易出现直接将某个节点的 Storm 文件直接复制到新节点的情况 —— 译者注)。 “Multiple defaults.yaml found” 错误 可能出现的现象: 在使用storm jar命令部署拓扑时出现此错误 解决方案: 你很可能在拓扑的 jar 包中包含了 Storm 自身的 jar 包。注意,在打包拓扑时,请不要将 Storm 自身的 jar 包加入,因为 Storm 已经在它的 classpath 中提供了这些 jar 包。 运行 storm jar 命令时出现 “NoSuchMethorError” 可能出现的现象: 运行storm jar命令时出现奇怪的 “NoSuchMethodError” 解决方案: 这可能是由于你部署拓扑的 Storm 版本与你构建拓扑时使用的 Storm 版本不同。请确保你编译拓扑时使用的 Storm 版本与你运行拓扑的 Storm 客户端版本相同。 Kryo ConcurrentModificationException 可能出现的现象: 系统运行时出现如下的异常堆栈跟踪信息 java.lang.RuntimeException: java.util.ConcurrentModificationException at backtype.storm.utils.DisruptorQueue.consumeBatchToCursor(DisruptorQueue.java:84) at backtype.storm.utils.DisruptorQueue.consumeBatchWhenAvailable(DisruptorQueue.java:55) at backtype.storm.disruptor$consume_batch_when_available.invoke(disruptor.clj:56) at backtype.storm.disruptor$consume_loop_STAR_$fn__1597.invoke(disruptor.clj:67) at backtype.storm.util$async_loop$fn__465.invoke(util.clj:377) at clojure.lang.AFn.run(AFn.java:24) at java.lang.Thread.run(Thread.java:679) Caused by: java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:390) at java.util.LinkedHashMap$EntryIterator.next(LinkedHashMap.java:409) at java.util.LinkedHashMap$EntryIterator.next(LinkedHashMap.java:408) at java.util.HashMap.writeObject(HashMap.java:1016) at sun.reflect.GeneratedMethodAccessor17.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:616) at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:959) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1480) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1416) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346) at backtype.storm.serialization.SerializableSerializer.write(SerializableSerializer.java:21) at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:554) at com.esotericsoftware.kryo.serializers.CollectionSerializer.write(CollectionSerializer.java:77) at com.esotericsoftware.kryo.serializers.CollectionSerializer.write(CollectionSerializer.java:18) at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:472) at backtype.storm.serialization.KryoValuesSerializer.serializeInto(KryoValuesSerializer.java:27) 解决方案: 这个信息表示你在将一个可变的对象作为 tuple 发送出去。你发送到 outputcollector 中的所有对象必须是非可变的。这个错误表明对象在被序列化并发送到网络中时你的 bolt 正在修改这个对象。 Storm 中的 NullPointerException 可能出现的现象: Storm 运行中出现了如下的 NullPointerException java.lang.RuntimeException: java.lang.NullPointerException at backtype.storm.utils.DisruptorQueue.consumeBatchToCursor(DisruptorQueue.java:84) at backtype.storm.utils.DisruptorQueue.consumeBatchWhenAvailable(DisruptorQueue.java:55) at backtype.storm.disruptor$consume_batch_when_available.invoke(disruptor.clj:56) at backtype.storm.disruptor$consume_loop_STAR_$fn__1596.invoke(disruptor.clj:67) at backtype.storm.util$async_loop$fn__465.invoke(util.clj:377) at clojure.lang.AFn.run(AFn.java:24) at java.lang.Thread.run(Thread.java:662) Caused by: java.lang.NullPointerException at backtype.storm.serialization.KryoTupleSerializer.serialize(KryoTupleSerializer.java:24) at backtype.storm.daemon.worker$mk_transfer_fn$fn__4126$fn__4130.invoke(worker.clj:99) at backtype.storm.util$fast_list_map.invoke(util.clj:771) at backtype.storm.daemon.worker$mk_transfer_fn$fn__4126.invoke(worker.clj:99) at backtype.storm.daemon.executor$start_batch_transfer__GT_worker_handler_BANG_$fn__3904.invoke(executor.clj:205) at backtype.storm.disruptor$clojure_handler$reify__1584.onEvent(disruptor.clj:43) at backtype.storm.utils.DisruptorQueue.consumeBatchToCursor(DisruptorQueue.java:81) ... 6 more 解决方案: 这个问题是由于多个线程同时调用OutputCollector中的方法造成的。Storm 中所有的 emit、ack、fail 方法必须在同一个线程中运行。出现这个问题的一种场景是在一个IBasicBolt中创建了一个独立的线程。由于IBasicBolt会在execute方法调用之后自动调用ack,所以这就会出现多个线程同时使用OutputCollector的情况,进而抛出这个异常。也就是说,在使用IBasicBolt时,所有的消息发送操作必须在同一个线程的execute方法中执行。 转载自并发编程网 - ifeve.com

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

Apache Storm 官方文档 —— 序列化

本文阐述了 Storm 0.6.0 以上版本的序列化机制。在低于 0.6.0 版本的 Storm 中使用了另一种序列化系统,详细信息可以参考Serialization (prior to 0.6.0)一文。 Storm 中的 tuple 可以包含任何类型的对象。由于 Storm 是一个分布式系统,所以在不同的任务之间传递消息时 Storm 必须知道怎样序列化、反序列化消息对象。 Storm 使用Kryo对对象进行序列化。Kryo 是一个生成小序列的灵活、快速的序列化库。 Storm 本身支持基础类型、字符串、字节数组、ArrayList、HashMap、HashSet 以及 Clojure 的集合类型的序列化。如果你需要在 tuple 中使用其他的对象类型,你就需要注册一个自定义的序列化器。 动态类型 在 tuple 中没有对各个域(field)的直接类型声明。你需要将对象放入对应的域中,然后 Storm 可以动态地实现对象的序列化。在学习序列化接口之前,我们先来了解一下为什么 Storm 的 tuple 是动态类型化的。 为 tuple 的 fields 增加静态类型会大幅增加 Storm 的 API 的复杂度。比如 Hadoop 就将它的 key 和 value 都静态化了,这就要求用户自己添加大量的注解。使用 Hadoop 的 API 非常繁琐,而相应的“类型安全”并不会为应用带来多大的好处。相对的,动态类型就非常易于使用。 更进一步而言,也不可能有什么合理的方法将 Storm 的 tuple 的类型静态化。假如一个 Bolt 订阅了多个 stream,从这些 stream 传入的 tuple 很可能都带有不同的类型。在 Bolt 的 execute 方法接收到一个 tuple 的时候,这个 tuple 可能来自任何一个 stream,也可能包含各种组合类型。也许你可以使用某种反射机制来为 bolt 订阅的每个 stream 声明一个方法类处理 tuple,但是 Storm 可以提供一种更简单、更直接的动态类型机制来解决这个问题。 最后要说明的是,Storm 使用动态类型定义的另一个原因就是为了支持 Clojure、JRuby 这样的动态类型语言。 定制序列化 前面已经提到,Storm 使用 Kryo 来处理序列化。如果要实现自定义的序列化生成器,你需要注册一个新的机遇 Kryo 的序列化生成器。强烈建议读者先仔细阅读Kryo 主页来理解它是怎样处理自定义的序列化的。 可以通过拓扑的topology.kryo.register属性来添加自定义序列化生成器。该属性接收一个注册列表,每个注册项都可以使用以下两种注册格式中的一种格式: 只有一个待注册的类的名称。在这种情况下,Storm 会使用 Kryo 的FieldsSerializer来序列化该类。这也许并不一定是该类的最优化方式 —— 可以查看 Kryo 的文档来了解更多细节内容。 一个包含待注册的类的名称和对应的序列化实现类名称,该序列化实现类实现了com.esotericsoftware.kryo.Serializer接口。 我们来看一个例子: topology.kryo.register: - com.mycompany.CustomType1 - com.mycompany.CustomType2: com.mycompany.serializer.CustomType2Serializer - com.mycompany.CustomType3 这里com.mycompany.CustomType1和com.mycompany.CustomType3会使用FieldsSerializer,而com.mycompany.CustomType2则会使用com.mycompany.serializer.CustomType2Serializer来实现序列化。 在拓扑的配置中,Storm 提供了用于注册序列化生成器的选项。Config类有一个registerSerialization方法可以将序列化生成器注册到配置中。 Config 中有一个更高级的配置项叫做Config.TOPOLOGY_SKIP_MISSING_KRYO_REGISTRATIONS。如果你将该项设置为 true,Storm 会忽略掉所有已注册但是在拓扑的 classpath 中没有相应的代码的序列化器。如果不这么做,Storm 会在无法查找到序列化器的时候抛出错误。如果你在集群中运行有多个拓扑并且每个拓扑都有不同的序列化器,但是你又想要在storm.yaml中声明好所有的序列化器,在这种情况下这个配置项会有很大的帮助。 Java 序列化 如果 Storm 发现了一个没有注册序列化器的类型,它会使用 Java 自带的序列化器。如果这个对象无法被 Java 序列化器序列化,Storm 就会抛出异常。 注意,Java 自身的序列化机制非常耗费资源,而且不管在 CPU 的性能上还是在序列化对象的大小上都没有优势。强烈建议读者在生产环境中运行拓扑的时候注册一个自定义的序列化器。保留 Java 的序列化机制主要为了便于设计新拓扑的原型。 你可以通过将Config.TOPOLOGY_FALL_BACK_ON_JAVA_SERIALIZATION配置为 false 的方式来将序列化器回退到 Java 的序列化机制。 特定组件的序列化注册 Storm 0.7.0 支持对特定组件的配置(详情请参阅Storm 的配置一文)。当然,如果某个组件定义了一个序列化器,这个序列化器也需要能够支持其他的 bolt —— 否则,后续的 bolt 将会无法接收来自该组件的消息! 在拓扑提交的时候,拓扑会选择一组序列化器用于在所有的组件间传递消息。这是通过将特定组件的序列化器注册信息与普通的序列化器信息融合在一起实现的。如果两个组件为同一个类定义了两个序列化器,Storm 会从中任意选择一个。 如果在两个组件的序列化器注册信息冲突的时候需要强制使用一个序列化器,可以在拓扑级的配置中定义你想要的序列化器。对于序列化器的注册信息,拓扑中配置的值是优先于具体组件的配置的。 转载自并发编程网 - ifeve.com

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

Apache Storm 官方文档 —— Storm 与 Kestrel

本文说明了如何使用 Storm 从 Kestrel 集群中消费数据。 前言 Storm 本教程中使用了storm-kestrel项目和storm-starter项目中的例子。建议读者将这几个项目 clone 到本地,并动手运行其中的例子。 Kestrel 本文假定读者可以如此项目所述在本地运行一个 Kestrel 集群。 Kestrel 服务器与队列 Kestrel 服务中包含有一组消息队列。Kestrel 队列是一种非常简单的消息队列,可以运行于 JVM 上,并使用 memcache 协议(以及一些扩展)与客户端交互。详情可以参考storm-kestrel项目中的KestrelThriftClient类的实现。 每个队列均严格遵循先入先出的规则。为了提高服务性能,数据都是缓存在系统内存中的;不过,只有开头的 128MB 是保存在内存中的。在服务停止的时候,队列的状态会保存到一个日志文件中。 请参阅此文了解更多详细信息。 Kestrel 具有 * 快速 * 小巧 * 持久 * 可靠 等特点。 例如,Twitter 就使用 Kestrel 作为消息系统的核心环节,此文中介绍了相关信息。 ** 向 Kestrel 中添加数据 首先,我们需要一个可以向 Kestrel 的队列添加数据的程序。下述方法使用了storm-kestrel项目中的KestrelClient的实现。该方法从一个包含 5 个句子的数组中随机选择一个句子添加到 Kestrel 的队列中。 private static void queueSentenceItems(KestrelClient kestrelClient, String queueName) throws ParseError, IOException { String[] sentences = new String[] { "the cow jumped over the moon", "an apple a day keeps the doctor away", "four score and seven years ago", "snow white and the seven dwarfs", "i am at two with nature"}; Random _rand = new Random(); for(int i=1; i<=10; i++){ String sentence = sentences[_rand.nextInt(sentences.length)]; String val = "ID " + i + " " + sentence; boolean queueSucess = kestrelClient.queue(queueName, val); System.out.println("queueSucess=" +queueSucess+ " [" + val +"]"); } } 从 Kestrel 中移除数据 此方法从一个队列中取出一个数据,但并不把该数据从队列中删除: private static void dequeueItems(KestrelClient kestrelClient, String queueName) throws IOException, ParseError { for(int i=1; i<=12; i++){ Item item = kestrelClient.dequeue(queueName); if(item==null){ System.out.println("The queue (" + queueName + ") contains no items."); } else { byte[] data = item._data; String receivedVal = new String(data); System.out.println("receivedItem=" + receivedVal); } } 此方法会从队列中取出并移除数据: private static void dequeueAndRemoveItems(KestrelClient kestrelClient, String queueName) throws IOException, ParseError { for(int i=1; i<=12; i++){ Item item = kestrelClient.dequeue(queueName); if(item==null){ System.out.println("The queue (" + queueName + ") contains no items."); } else { int itemID = item._id; byte[] data = item._data; String receivedVal = new String(data); kestrelClient.ack(queueName, itemID); System.out.println("receivedItem=" + receivedVal); } } } 向 Kestrel 中连续添加数据 下面的程序可以向本地 Kestrel 服务的一个sentence_queue队列中连续添加句子,这也是我们的最后一个程序。 可以在命令行窗口中输入一个右中括号]并回车来停止程序。 import java.io.IOException; import java.io.InputStream; import java.util.Random; import backtype.storm.spout.KestrelClient; import backtype.storm.spout.KestrelClient.Item; import backtype.storm.spout.KestrelClient.ParseError; public class AddSentenceItemsToKestrel { /** * @param args */ public static void main(String[] args) { InputStream is = System.in; char closing_bracket = ']'; int val = closing_bracket; boolean aux = true; try { KestrelClient kestrelClient = null; String queueName = "sentence_queue"; while(aux){ kestrelClient = new KestrelClient("localhost",22133); queueSentenceItems(kestrelClient, queueName); kestrelClient.close(); Thread.sleep(1000); if(is.available()>0){ if(val==is.read()) aux=false; } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ParseError e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("end"); } } 使用 KestrelSpout 下面的拓扑使用KestrelSpout从一个 Kestrel 队列中读取句子,并将句子分割成若干个单词(Bolt:SplitSentence),然后输出每个单词出现的次数(Bolt:WordCount)。数据处理的细节可以参考消息的可靠性保证一文。 TopologyBuilder builder = new TopologyBuilder(); builder.setSpout("sentences", new KestrelSpout("localhost",22133,"sentence_queue",new StringScheme())); builder.setBolt("split", new SplitSentence(), 10) .shuffleGrouping("sentences"); builder.setBolt("count", new WordCount(), 20) .fieldsGrouping("split", new Fields("word")); 运行 首先,以生产模式或者开发者模式启动你的本地 Kestrel 服务。 然后,等待大约 5 秒钟以防出现网络连接异常。 现在可以运行向队列中添加数据的程序,并启动 Storm 拓扑。程序启动的顺序并不重要。 如果你以 TOPOLOGY_DEBUG 模式运行拓扑你会观察到拓扑中 tuple 发送的细节信息。 转载自并发编程网 - ifeve.com

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

Apache Storm 官方文档 —— 配置开发环境

本文详细讲解了配置 Storm 开发环境的相关信息。简单地说,配置过程包含以下几个步骤: 下载Storm 发行版,将其解压缩并复制到你的PATH环境变量的bin目录中(也可以根据需要自定义安装目录 —— 译者注); 如果需要在远程集群中运行拓扑,则需要在~/.storm/storm.yaml文件中配置好集群的相关信息。 上述几步的详细内容如下。 什么是开发环境? Storm 包含两种操作模式:本地模式与远程模式(即集群模式 —— 译者注)。在本地模式下,你可以在本地机器上的一个进程中完成所有的开发、测试拓扑的工作。而在远程模式下,为了运行拓扑,你需要先向服务器集群提交该拓扑。 Storm 的开发环境已经为你准备好了一切,因此,你可以在本地模式下完成开发、测试拓扑的工作,将拓扑打包并提交到远程服务器,并在远程服务器集群上运行或者终止拓扑。 我们再来回顾一下本地机器与远程集群之间的关系。Storm 集群是由一个称为 “Nimbus” 的主节点管理的。本地机器通过与 Nimbus 通信来提交代码(代码已经打包为 jar 格式),这样代码文件中包含的拓扑就可以在集群中运行。Nimbus 会小心地维护着代码在集群中的分布式结构,并为待运行的拓扑分配 worker。本地机器可以使用一个称为storm的命令行客户端来与 Nimbus 进行通信。不过,storm客户端仅用于远程模式,不能用于本地模式下开发、测试拓扑。 在本地机器上安装 Storm 如果要从本地机器上直接向远程集群提交拓扑,你需要在本地机器上安装 Storm 程序。本地的 Storm 程序可以提供与远程集群交互的storm客户端。在安装本地 Storm 之前,你需要从这里下载一个 Storm 安装程序并将其解压到你的电脑的某个位置。然后将 Storm 的bin/目录添加到你的PATH环境变量中,确保bin/storm脚本可以直接运行。 在本地机器上安装的 Storm 仅能用于与远程集群的交互。对于本地模式下的开发、测试拓扑,推荐使用 Maven 来将 Storm 添加到你的项目的开发依赖中。关于 Maven 的使用请参考此文。 在远程集群上开始/终止拓扑的运行 在上一步中我们已经安装好了本地的storm客户端。接下来就需要告诉客户端需要连接哪一个 Storm 集群。这可以通过在~/.storm/storm.yaml文件中填写 Storm 集群的主节点的 host 地址来实现: nimbus.host: "123.45.678.890" 另外,如果你在 AWS 上应用storm-deploy项目来配置 Storm 集群,它会自动配置好你的~/.storm/storm.yaml文件。你也可以使用attach命令手动配置附属的 Storm 集群(或者在多个集群之间切换): lein run :deploy --attach --name mystormcluster 更多内容请参考 storm-deploy 项目的wiki。 转载自并发编程网 - ifeve.com

资源下载

更多资源
Mario

Mario

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

Nacos

Nacos

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

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文件系统,支持十年生命周期更新。

用户登录
用户注册