ListView之多种类型Item

ListView之多种类型Item一、概述一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。本文重点介绍

大家好,又见面了,我是你们的朋友全栈君。

一、概述

一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。

拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。

本文重点介绍后者,也就是多类型item的listview的实现思路和方法,比如实现一个这样的聊天会话页面:

<span role="heading" aria-level="2">ListView之多种类型Item

 

二、实现思路

2.1 第一种思路用“一种类型”变相实现多种类型

这种思路其实与 ListView之点击展开菜单 这篇文章的原理一样,每个item的布局都包含所有类型的元素:

 <span role="heading" aria-level="2">ListView之多种类型Item

对于每个item,根据实际类型,控制“日期”、“发出的消息”、“接收的消息”这三部分的显示/隐藏即可。

这种思路的优势在于好理解,是单一类型的listview的扩展,却并不适合本文描述的应用场景。

因为每个item实际上只会显示“日期”、“发出的消息”、“接收的消息”中的一种,所以每个item都inflate出来一个“全家桶”layout再隐藏其中的两个,实在是一种资源浪费。

 

2.2 第二种思路:利用Adapter原生支持的多类型

其实 android.widget.Adapter 类已经原生支持了多种类型item的模式,并提供了 int getViewTypeCount();  int getItemViewType(int position); 两个方法

只不过在 android.widget.BaseAdapter 中对这两个方法进行了如下的默认实现:

1 public int getViewTypeCount() {
2     return 1;
3 }
4 
5 public int getItemViewType(int position) {
6     return 0;
7 }

那我们要做的就是根据实际的数据,对这两个方法进行正确的返回。

本文采用第二种思路实现多种类型item的listview。

   [转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

三、开始干活

3.1 首先准备好listview的数据和三种item布局

ListViewMultiTypeActivity$JsonListData:

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1     private static class JsonListData {
 2         public static class Message {
 3             public static final int TYPE_COUNT = 3;
 4             public static final int TYPE_DATE = 0x00;
 5             public static final int TYPE_TXT_SENT = 0x01;
 6             public static final int TYPE_TXT_RECV = 0x02;
 7             public int type;
 8             public String txt;
 9             public long time;
10         }
11         public List<Message> messages = new ArrayList<Message>();
12     }

View Code

listview_multitype_data.json:

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

{
    "messages": [
        {
            "type": 0,
            "time": 1467284175
        },
        {
            "type": 1,
            "txt": "你好"
        },
        {
            "type": 2,
            "txt": "你才好"
        },
        {
            "type": 1,
            "txt": "对话,指两个或更多的人用语言交谈,多指小说或戏剧里的人物之间的"
        },
        {
            "type": 2,
            "txt": "京东童书节低至300减180"
        },
        {
            "type": 1,
            "txt": "http://www.cnblogs.com/snser/"
        },
        {
            "type": 2,
            "txt": "京东商城目前已成长为中国最大的自营式电商企业,2015年第三季度在中国自营式B2C电商市场的占有率为56.9%。"
        },
        {
            "type": 0,
            "time": 1467289175
        },
        {
            "type": 1,
            "txt": "京东金融现已建立七大业务板块,分别是供应链金融、消费金融、众筹、财富管理、支付、保险、证券,陆续推出了京保贝、白条、京东钱包、小金库、京小贷、产品众筹、私募股权融资、小白理财等创新产品"
        },
        {
            "type": 2,
            "txt": "您目前没有新消息"
        },
        {
            "type": 2,
            "txt": "黑炎凝聚,竟是直接化为了一头仰天长啸的黑色巨鸟,而后它仿佛是发现了牧尘飘荡的意识,化为一道黑色火焰,眼芒凶狠的对着他的意识暴冲而来"
        },
        {
            "type": 0,
            "time": 1467294175
        },
        {
            "type": 2,
            "txt": "国务院罕见派出民间投资督查组:活力不够形势严峻"
        },
        {
            "type": 1,
            "txt": "那一道清鸣,并不算太过的响亮,但却是让得牧尘如遭雷击,整个身体都是僵硬了下来,脑子里回荡着嗡嗡的声音。"
        },
        {
            "type": 2,
            "txt": "据海关统计,今年前4个月,我国进出口总值7.17万亿元人民币,比去年同期(下同)下降4.4%。其中,出口4.14万亿元,下降2.1%;进口3.03万亿元,下降7.5%;贸易顺差1.11万亿元,扩大16.5%。"
        },
        {
            "type": 1,
            "txt": "在介绍算法的时空复杂度分析方法前,我们先来介绍以下如何来量化算法的实际运行性能,这里我们选取的衡量算法性能的量化指标是它的实际运行时间。"
        },
        {
            "type": 2,
            "txt": "你拍一"
        },
        {
            "type": 2,
            "txt": "我拍一"
        },
        {
            "type": 1,
            "txt": "一二三四五六七"
        }
    ]
}

View Code

ListViewMultiTypeActivity.onCreate 

 1     protected void onCreate(Bundle savedInstanceState) {
 2         super.onCreate(savedInstanceState);
 3         setContentView(R.layout.listview_multi_type);
 4         
 5         JsonListData data = null;
 6         try {
 7             InputStream is = getResources().getAssets().open("listview_multitype_data.json");
 8             InputStreamReader isr = new InputStreamReader(is);
 9             Gson gson = new GsonBuilder().serializeNulls().create();
10             data = gson.fromJson(isr, JsonListData.class);
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14         
15         if (data != null && data.messages != null) {
16             mList = (ListView)findViewById(R.id.listview_multi_type_list);
17             mList.setAdapter(new MultiTypeAdapter(ListViewMultiTypeActivity.this, data.messages));
18         }
19     }

listview_multi_type_item_date.xml

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_date_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:layout_gravity="center_horizontal"
14         android:layout_margin="6dp"
15         android:padding="3dp"
16         android:background="#CCCCCC"
17         android:textColor="@android:color/white"
18         android:textSize="12sp"
19         android:text="2015年3月25日 18:44" />
20     
21 </LinearLayout>

View Code

listview_multi_type_item_txt_sent.xml

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_txt_sent_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:maxWidth="250dp"
14         android:layout_gravity="right"
15         android:layout_margin="4dp"
16         android:paddingTop="5dp"
17         android:paddingBottom="5dp"
18         android:paddingRight="10dp"
19         android:paddingLeft="5dp"
20         android:background="@drawable/listview_multi_type_item_txt_sent_bg"
21         android:textColor="@android:color/black"
22         android:textSize="13sp"
23         android:text="发出的消息"
24         android:autoLink="web" />
25     
26 </LinearLayout>

View Code

listview_multi_type_item_txt_recv.xml

<span role="heading" aria-level="2">ListView之多种类型Item
<span role="heading" aria-level="2">ListView之多种类型Item

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#EEEEEE"
 6     android:orientation="vertical"
 7     tools:context="${relativePackage}.${activityClass}" >
 8 
 9     <TextView
10         android:id="@+id/listview_multi_type_item_txt_recv_txt"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content"
13         android:maxWidth="250dp"
14         android:layout_gravity="left"
15         android:layout_margin="4dp"
16         android:paddingTop="5dp"
17         android:paddingBottom="5dp"
18         android:paddingRight="5dp"
19         android:paddingLeft="10dp"
20         android:background="@drawable/listview_multi_type_item_txt_recv_bg"
21         android:textColor="@android:color/black"
22         android:textSize="13sp"
23         android:text="接收的消息"
24         android:autoLink="web" />
25     
26 </LinearLayout>

View Code

 

3.2 重头戏在于Adapter的处理

  1     private class MultiTypeAdapter extends BaseAdapter {
  2         private LayoutInflater mInflater;
  3         private List<JsonListData.Message> mMessages;
  4         private SimpleDateFormat mSdfDate = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss", Locale.getDefault());
  5         
  6         public MultiTypeAdapter(Context context, List<JsonListData.Message> messages) {
  7             mInflater = LayoutInflater.from(context);
  8             mMessages = messages;
  9         }
 10         
 11         private class DateViewHolder {
 12             public DateViewHolder(View viewRoot) {
 13                 date = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_date_txt);
 14             }
 15             public TextView date;
 16         }
 17         
 18         private class TxtSentViewHolder {
 19             public TxtSentViewHolder(View viewRoot) {
 20                 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_sent_txt);
 21             }
 22             public TextView txt;
 23         }
 24         
 25         private class TxtRecvViewHolder {
 26             public TxtRecvViewHolder(View viewRoot) {
 27                 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_recv_txt);
 28             }
 29             public TextView txt;
 30         }
 31         
 32         @Override
 33         public int getViewTypeCount() {
 34             return JsonListData.Message.TYPE_COUNT;
 35         }
 36         
 37         @Override
 38         public int getItemViewType(int position) {
 39             return getItem(position).type;
 40         }
 41         
 42         @Override
 43         public int getCount() {
 44             return mMessages.size();
 45         }
 46 
 47         @Override
 48         public JsonListData.Message getItem(int position) {
 49             return mMessages.get(position);
 50         }
 51 
 52         @Override
 53         public long getItemId(int position) {
 54             return position;
 55         }
 56 
 57         @Override
 58         public View getView(int position, View convertView, ViewGroup parent) {
 59             switch (getItemViewType(position)) {
 60                 case JsonListData.Message.TYPE_DATE:
 61                     return handleGetDateView(position, convertView, parent);
 62                 case JsonListData.Message.TYPE_TXT_SENT:
 63                     return handleGetTxtSentView(position, convertView, parent);
 64                 case JsonListData.Message.TYPE_TXT_RECV:
 65                     return handleGetTxtRecvView(position, convertView, parent);
 66                 default:
 67                     return null;
 68             }
 69         }
 70         
 71         private View handleGetDateView(int position, View convertView, ViewGroup parent) {
 72             if (convertView == null) {
 73                 convertView = mInflater.inflate(R.layout.listview_multi_type_item_date, parent, false);
 74                 convertView.setTag(new DateViewHolder(convertView));
 75             }
 76             if (convertView != null && convertView.getTag() instanceof DateViewHolder) {
 77                 final DateViewHolder holder = (DateViewHolder)convertView.getTag();
 78                 holder.date.setText(formatTime(getItem(position).time));
 79             }
 80             return convertView;
 81         }
 82         
 83         private View handleGetTxtSentView(int position, View convertView, ViewGroup parent) {
 84             if (convertView == null) {
 85                 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_sent, parent, false);
 86                 convertView.setTag(new TxtSentViewHolder(convertView));
 87             }
 88             if (convertView != null && convertView.getTag() instanceof TxtSentViewHolder) {
 89                 final TxtSentViewHolder holder = (TxtSentViewHolder)convertView.getTag();
 90                 holder.txt.setText(getItem(position).txt);
 91             }
 92             return convertView;
 93         }
 94         
 95         private View handleGetTxtRecvView(int position, View convertView, ViewGroup parent) {
 96             if (convertView == null) {
 97                 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_recv, parent, false);
 98                 convertView.setTag(new TxtRecvViewHolder(convertView));
 99             }
100             if (convertView != null && convertView.getTag() instanceof TxtRecvViewHolder) {
101                 final TxtRecvViewHolder holder = (TxtRecvViewHolder)convertView.getTag();
102                 holder.txt.setText(getItem(position).txt);
103             }
104             return convertView;
105         }
106         
107         private String formatTime(long time) {
108             return mSdfDate.format(new Date(time * 1000));
109         }
110     }

可以看到, int getViewTypeCount();  int getItemViewType(int position); 的处理是非常清晰的。

需要注意的在于,ViewType必须在 [0, getViewTypeCount() – 1] 范围内

 

3.3 ViewHolder为何能正确的工作

回顾一下单一类型的listview,其ViewHolder的工作机制在于系统会将滑出屏幕的item的view回收起来,并作为getView的第二个参数 convertView 传入。

那么,在多种类型的listview中,滑出屏幕的view与即将滑入屏幕的view类型很可能是不同的,那这么直接用不就挂了吗?

其实不然,android针对多种类型item的情况已经做好处理了,如果getView传入的 convertView 不为null,那它一定与当前item的view类型是匹配的。

所以,在3.2节中对ViewHolder的处理方式与单类型的listview并没有本质区别,却也能正常的工作。

  [转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

四、demo工程

保存下面的图片,扩展名改成 .zip 即可

<span role="heading" aria-level="2">ListView之多种类型Item

  [转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

五、番外篇 —— ListView回收机制简要剖析

在3.3节中简单介绍了android系统会处理好多类型item的回收和重用,那具体是怎么实现的呢?

下面简要剖析一下支持多种类型item的listview中,View回收的工作机制。

5.1 View回收站的初始化

ListView的父类AbsListView中定义了一个内部类RecycleBin,这个类维护了listview滑动过程中,view的回收和重用。

在ListView的 setAdapter 方法中,会通过调用 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()) 来初始化RecycleBin。

让我们看下RecycleBin中对应都做了什么:

 1         public void setViewTypeCount(int viewTypeCount) {
 2             if (viewTypeCount < 1) {
 3                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
 4             }
 5             //noinspection unchecked
 6             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
 7             for (int i = 0; i < viewTypeCount; i++) {
 8                 scrapViews[i] = new ArrayList<View>();
 9             }
10             mViewTypeCount = viewTypeCount;
11             mCurrentScrap = scrapViews[0];
12             mScrapViews = scrapViews;
13         }

看源码,说白了就是创建了一个大小为 getViewTypeCount() 数组 mScrapViews ,从而为每种类型的view维护了一个回收站,此外每种类型的回收站自身又是一个View数组。

这也就解释了为什么ViewType必须在 [0, getViewTypeCount() – 1] 范围内。

 

5.2 View回收站的构建和维护

AbsListView在滑动时,会调用 trackMotionScroll 方法:

 1     boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
 2         //...
 3         final boolean down = incrementalDeltaY < 0;
 4         //...
 5         if (down) {
 6             int top = -incrementalDeltaY;
 7             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
 8                 top += listPadding.top;
 9             }
10             for (int i = 0; i < childCount; i++) {
11                 final View child = getChildAt(i);
12                 if (child.getBottom() >= top) {
13                     break;
14                 } else {
15                     count++;
16                     int position = firstPosition + i;
17                     if (position >= headerViewsCount && position < footerViewsStart) {
18                         // The view will be rebound to new data, clear any
19                         // system-managed transient state.
20                         if (child.isAccessibilityFocused()) {
21                             child.clearAccessibilityFocus();
22                         }
23  mRecycler.addScrapView(child, position); 24                     }
25                 }
26             }
27         } else {
28             int bottom = getHeight() - incrementalDeltaY;
29             if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
30                 bottom -= listPadding.bottom;
31             }
32             for (int i = childCount - 1; i >= 0; i--) {
33                 final View child = getChildAt(i);
34                 if (child.getTop() <= bottom) {
35                     break;
36                 } else {
37                     start = i;
38                     count++;
39                     int position = firstPosition + i;
40                     if (position >= headerViewsCount && position < footerViewsStart) {
41                         // The view will be rebound to new data, clear any
42                         // system-managed transient state.
43                         if (child.isAccessibilityFocused()) {
44                             child.clearAccessibilityFocus();
45                         }
46  mRecycler.addScrapView(child, position); 47                     }
48                 }
49             }
50         }
51         //...
52     }

 trackMotionScroll 方法中,会根据不同的滑动方向,调用 addScrapView ,将滑出屏幕的view加到RecycleBin中:

 1         void addScrapView(View scrap, int position) {
 2             final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
 3             if (lp == null) {
 4                 return;
 5             }
 6 
 7             lp.scrappedFromPosition = position;
 8 
 9             // Remove but don't scrap header or footer views, or views that
10             // should otherwise not be recycled.
11             final int viewType = lp.viewType;
12             if (!shouldRecycleViewType(viewType)) {
13                 return;
14             }
15 
16             scrap.dispatchStartTemporaryDetach();
17 
18             // The the accessibility state of the view may change while temporary
19             // detached and we do not allow detached views to fire accessibility
20             // events. So we are announcing that the subtree changed giving a chance
21             // to clients holding on to a view in this subtree to refresh it.
22             notifyViewAccessibilityStateChangedIfNeeded(
23                     AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
24 
25             // Don't scrap views that have transient state.
26             final boolean scrapHasTransientState = scrap.hasTransientState();
27             if (scrapHasTransientState) {
28                 if (mAdapter != null && mAdapterHasStableIds) {
29                     // If the adapter has stable IDs, we can reuse the view for
30                     // the same data.
31                     if (mTransientStateViewsById == null) {
32                         mTransientStateViewsById = new LongSparseArray<View>();
33                     }
34  mTransientStateViewsById.put(lp.itemId, scrap); 35                 } else if (!mDataChanged) {
36                     // If the data hasn't changed, we can reuse the views at
37                     // their old positions.
38                     if (mTransientStateViews == null) {
39                         mTransientStateViews = new SparseArray<View>();
40                     }
41  mTransientStateViews.put(position, scrap); 42                 } else {
43                     // Otherwise, we'll have to remove the view and start over.
44                     if (mSkippedScrap == null) {
45                         mSkippedScrap = new ArrayList<View>();
46                     }
47                     mSkippedScrap.add(scrap);
48                 }
49             } else {
50                 if (mViewTypeCount == 1) {
51                     mCurrentScrap.add(scrap);
52                 } else {
53  mScrapViews[viewType].add(scrap); 54                 }
55 
56                 // Clear any system-managed transient state.
57                 if (scrap.isAccessibilityFocused()) {
58                     scrap.clearAccessibilityFocus();
59                 }
60 
61                 scrap.setAccessibilityDelegate(null);
62 
63                 if (mRecyclerListener != null) {
64                     mRecyclerListener.onMovedToScrapHeap(scrap);
65                 }
66             }
67         }

 addScrapView 方法中,被回收的view会根据其类型加入 mScrapViews 中。

特别的,如果这个view处于TransientState(瞬态,view正在播放动画或其他情况),则会被存入 mTransientStateViewsById  mTransientStateViews 

 

5.3 从View回收站获取View

Adapter的getView方法在AbsListView的 obtainView 中被调用:

 1     View obtainView(int position, boolean[] isScrap) {
 2         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
 3         isScrap[0] = false;
 4         View scrapView;
 5         scrapView = mRecycler.getTransientStateView(position);  6         if (scrapView == null) {
 7             scrapView = mRecycler.getScrapView(position);  8         }
 9         
10         View child;
11         if (scrapView != null) {
12             child = mAdapter.getView(position, scrapView, this); 13             if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
14                 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
15             }
16             if (child != scrapView) {
17  mRecycler.addScrapView(scrapView, position); 18                 if (mCacheColorHint != 0) {
19                     child.setDrawingCacheBackgroundColor(mCacheColorHint);
20                 }
21             } else {
22                 isScrap[0] = true;
23                 // Clear any system-managed transient state so that we can
24                 // recycle this view and bind it to different data.
25                 if (child.isAccessibilityFocused()) {
26                     child.clearAccessibilityFocus();
27                 }
28                 child.dispatchFinishTemporaryDetach();
29             }
30         } else {
31             child = mAdapter.getView(position, null, this); 32             if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
33                 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
34             }
35             if (mCacheColorHint != 0) {
36                 child.setDrawingCacheBackgroundColor(mCacheColorHint);
37             }
38         }
39         
40         //...
41         
42         return child;
43     }

可以看到,对于不处于TransientState的View,将会尝试通过 getScrapView 方法获取回收的View,如果有,就会作为参数传入Adatper的getView方法中。

 getScrapView 方法,其实就是先调用Adapter的 getItemViewType 方法取position对应的view类型,然后从 mScrapViews 中根据类型取view。

 1         View getScrapView(int position) {
 2             if (mViewTypeCount == 1) {
 3                 return retrieveFromScrap(mCurrentScrap, position);
 4             } else {
 5                 int whichScrap = mAdapter.getItemViewType(position);
 6                 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
 7                     return retrieveFromScrap(mScrapViews[whichScrap], position);
 8                 }
 9             }
10             return null;
11         }

至此,我们简要了解了多类型的listview中,是如何在滑动屏幕时回收view并进行重用的。

而如何维护每个类型item对应的View数组,以及TransientState的维护,本篇文章就不做详细介绍了,有兴趣的读者可以着重研究一下AbsListView的源码。

 

[转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html] 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/154873.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月4日 上午10:16
下一篇 2022年7月4日 上午10:16


相关推荐

  • Kail 开启ssh

    Kail 开启ssh一 kali 安装 ssh 服务 1 修改源 1root DGG vi etc apt sources list2debhttp http kali org kalikali rollingmainn freecontrib2 更新一下源 apt getupdate3 下载 ssh 服务包 apt getinstallss 打开 ssh 服务 1servicesshs 打开 ssh 服务 2servicesshs 查看状态 3ser

    2026年3月17日
    2
  • Spring AOP原理「建议收藏」

    Spring AOP原理「建议收藏」Spring AOP原理

    2022年4月25日
    50
  • Spring Cloud面试题(2020最新版)[通俗易懂]

    Spring Cloud面试题(2020最新版)[通俗易懂]文章目录为什么需要学习SpringCloud什么是SpringCloud设计目标与优缺点设计目标优缺点SpringCloud发展前景整体架构主要项目SpringCloudConfigSpringCloudNetflixSpringCloudBusSpringCloudConsulSpringCloudSecuritySpringCloudSleuthSpringCl…

    2022年5月7日
    66
  • mysql5.7卸载不干净_mysql卸载残留

    mysql5.7卸载不干净_mysql卸载残留一、在控制面板中卸载mysql软件,此时mysql没有卸载干净。二、卸载过后删除C:ProgramFiles(x86)MySQL该目录下剩余了所有文件,把mysql文件夹也删了三、windows+R运行“regedit”文件,打开注册表四、删除注册表:HKEY_LOCAL_MACHINESYSTEMControlSet001ServicesEventlogApplicationMySQL文件夹…

    2026年4月16日
    6
  • idea查看自己的激活码_在线激活「建议收藏」

    (idea查看自己的激活码)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html83…

    2022年3月27日
    214
  • Manus手机版

    Manus手机版

    2026年3月15日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号