zoukankan      html  css  js  c++  java
  • TouTiao开源项目 分析笔记6

    1.NewsChannelBean简单类笔记

    1.1.Comparable接口的实现和使用

      参考文章:Comparable接口的实现和使用。

      因为NewsChannelBean实现了Comparable<NewsChannelBean>

    public class NewsChannelBean implements Comparable<NewsChannelBean>

      一开始不明白为什么要实现这个接口。

      ==>其实就是强行对实现它的每个类的对象进行整体排序。

      

      之后会报错,因为没有重写比较函数。

      再重写一个这个函数进行大小比较

     @Override
        public int compareTo(@NonNull NewsChannelBean o) {
            return this.position - o.getPosition();
        }

      这个bean类有一个position。所以间接利用position进行了大小的排序。写得非常妙。

      

    1.2.重写一个equals函数判断两个对象是否相等 

     @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            NewsChannelBean bean = (NewsChannelBean) o;
            if (isEnable != bean.isEnable)
                return false;
            if (position != bean.position)
                return false;
            if (channelId != null ? 
              !channelId.equals(bean.channelId) : bean.channelId != null) return false; return channelName != null ?
              channelName.equals(bean.channelName) : bean.channelName == null; }

      整个比较过程就是按照顺序一个一个比较,直到全部都相同为止。

    1.3.重写equals的时候,必须重写hashCode函数。

      参考文章:浅谈Java中的hashcode方法。 

    下面这段话摘自Effective Java一书:
    
    在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终
    如一地返回同一个整数。 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。   对于第二条和第三条很好理解,但是第一条,很多时候就会忽略。在《Java编程思想》一书中的P495页也有同第一条类似的
    一段话:   “设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。
    如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,
    那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,
    因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

      这里重写返回哈希的函数为:

     @Override
        public int hashCode() {
            int result = channelId != null ? channelId.hashCode() : 0;
            result = 31 * result + (channelName != null ? channelName.hashCode() : 0);
            result = 31 * result + isEnable;
            result = 31 * result + position;
            return result;
        }

    1.4.所以整个bean类就搞定了。

    package com.meiji.toutiao.bean.news;
    
    import android.support.annotation.NonNull;
    
    /**
     * Created by Meiji on 2017/3/10.
     */
    
    public class NewsChannelBean implements Comparable<NewsChannelBean> {
    
        private String channelId;
        private String channelName;
        private int isEnable;
        private int position;
    
        public String getChannelId() {
            return channelId;
        }
    
        public void setChannelId(String channelId) {
            this.channelId = channelId;
        }
    
        public String getChannelName() {
            return channelName;
        }
    
        public void setChannelName(String channelName) {
            this.channelName = channelName;
        }
    
        public int getIsEnable() {
            return isEnable;
        }
    
        public void setIsEnable(int isEnable) {
            this.isEnable = isEnable;
        }
    
        public int getPosition() {
            return position;
        }
    
        public void setPosition(int position) {
            this.position = position;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            NewsChannelBean bean = (NewsChannelBean) o;
            if (isEnable != bean.isEnable)
                return false;
            if (position != bean.position)
                return false;
            if (channelId != null ? !channelId.equals(bean.channelId) : bean.channelId != null)
                return false;
            return channelName != null ? channelName.equals(bean.channelName) : bean.channelName == null;
        }
    
        @Override
        public int hashCode() {
            int result = channelId != null ? channelId.hashCode() : 0;
            result = 31 * result + (channelName != null ? channelName.hashCode() : 0);
            result = 31 * result + isEnable;
            result = 31 * result + position;
            return result;
        }
    
        @Override
        public int compareTo(@NonNull NewsChannelBean o) {
            return this.position - o.getPosition();
        }
    }
    View Code


    2.构造NewsChannelDao处理底层数据增删查改

    2.1.获取可以写数据库的实例。 

        private SQLiteDatabase db;
    
        public NewsChannelDao() {
            this.db = DatabaseHelper.getDatabase();
        }

      这个DatabaseHelper是一个自定义的数据库帮助类,用来产生可以写数据库的实例。而且要求加上同步锁。

      //这里是DatabaseHelper类
      public
    static synchronized SQLiteDatabase getDatabase() { if (db == null) { db = getInstance().getWritableDatabase(); } return db; }

      

    2.2.添加初始数据。

    public void addInitData() {
            String categoryId[] = InitApp.AppContext.getResources().getStringArray
                                          (R.array.mobile_news_id); String categoryName[]
    = InitApp.AppContext.getResources().getStringArray
                                          (R.array.mobile_news_name);
    for (int i = 0; i < 8; i++) { add(categoryId[i], categoryName[i], Constant.NEWS_CHANNEL_ENABLE, i); } for (int i = 8; i < categoryId.length; i++) { add(categoryId[i], categoryName[i], Constant.NEWS_CHANNEL_DISABLE, i); } }

      这个R.array.mobile_news_id是自定义的新闻类别id

      这个R.array.mobile_news_name是自定义的新闻名称

    2.3.添加add方法。

    public boolean add(String channelId, String channelName, int isEnable, int position) {
            ContentValues values = new ContentValues();
            values.put(NewsChannelTable.ID, channelId);
            values.put(NewsChannelTable.NAME, channelName);
            values.put(NewsChannelTable.IS_ENABLE, isEnable);
            values.put(NewsChannelTable.POSITION, position);
            long result = db.insert(NewsChannelTable.TABLENAME, null, values);
            return result != -1;
        }

       这里用ContentValues整理插入数据库的数据。

    2.4.查询方法。 

     public List<NewsChannelBean> query(int isEnable) {
            Cursor cursor = db.query(NewsChannelTable.TABLENAME, null, 
          NewsChannelTable.IS_ENABLE + "=?", new String[]{isEnable + ""}, null, null, null); List<NewsChannelBean> list = new ArrayList<>(); while (cursor.moveToNext()) { NewsChannelBean bean = new NewsChannelBean(); bean.setChannelId(cursor.getString(NewsChannelTable.ID_ID)); bean.setChannelName(cursor.getString(NewsChannelTable.ID_NAME)); bean.setIsEnable(cursor.getInt(NewsChannelTable.ID_ISENABLE)); bean.setPosition(cursor.getInt(NewsChannelTable.ID_POSITION)); list.add(bean); } cursor.close(); return list; }

    2.5.查询所有

    public List<NewsChannelBean> queryAll() {
            Cursor cursor = db.query(NewsChannelTable.TABLENAME, null, null, null, null, null, null);
            List<NewsChannelBean> list = new ArrayList<>();
            while (cursor.moveToNext()) {
                NewsChannelBean bean = new NewsChannelBean();
                bean.setChannelId(cursor.getString(NewsChannelTable.ID_ID));
                bean.setChannelName(cursor.getString(NewsChannelTable.ID_NAME));
                bean.setIsEnable(cursor.getInt(NewsChannelTable.ID_ISENABLE));
                bean.setPosition(cursor.getInt(NewsChannelTable.ID_POSITION));
                list.add(bean);
            }
            cursor.close();
            return list;
        }

    2.6.移除所有

        public boolean removeAll() {
            int result = db.delete(NewsChannelTable.TABLENAME, null, null);
            return result != -1;
        }

    2.7.NewsChannelDao源代码

    package com.jasonjan.headnews.database.dao;
    
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    
    import com.jasonjan.headnews.R;
    import com.jasonjan.headnews.bean.news.NewsChannelBean;
    import com.jasonjan.headnews.database.helper.DatabaseHelper;
    import com.jasonjan.headnews.database.table.NewsChannelTable;
    import com.jasonjan.headnews.global.Constant;
    import com.jasonjan.headnews.global.InitApp;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by JasonJan on 2017/12/4.
     */
    
    public class NewsChannelDao {
        private SQLiteDatabase db;
        public NewsChannelDao(){
            this.db= DatabaseHelper.getDatabase();
        }
    
        public void addInitData(){
            String categoryId[]= InitApp.AppContext.getResources().getStringArray(R.array.mobile_news_id);
            String categoryName[]=InitApp.AppContext.getResources().getStringArray(R.array.mobile_news_name);
            for(int i=0;i<8;i++){
                add(categoryId[i], categoryName[i], Constant.NEWS_CHANNEL_ENABLE, i);
            }
            for (int i = 8; i < categoryId.length; i++) {
                add(categoryId[i], categoryName[i], Constant.NEWS_CHANNEL_DISABLE, i);
            }
        }
    
        public boolean add(String channelId,String channelName,int isEnable,int position){
            ContentValues values=new ContentValues();
            values.put(NewsChannelTable.ID,channelId);
            values.put(NewsChannelTable.NAME,channelName);
            values.put(NewsChannelTable.IS_ENABLE,isEnable);
            values.put(NewsChannelTable.POSITION,position);
            long result=db.insert(NewsChannelTable.TABLENAME,null,values);
            return result!=-1;
        }
    
        public List<NewsChannelBean> query(int isEnable) {
            Cursor cursor = db.query(NewsChannelTable.TABLENAME, null, NewsChannelTable.IS_ENABLE +
                    "=?", new String[]{isEnable + ""}, null, null, null);
            List<NewsChannelBean> list = new ArrayList<>();
            while (cursor.moveToNext()) {
                NewsChannelBean bean = new NewsChannelBean();
                bean.setChannelId(cursor.getString(NewsChannelTable.ID_ID));
                bean.setChannelName(cursor.getString(NewsChannelTable.ID_NAME));
                bean.setIsEnable(cursor.getInt(NewsChannelTable.ID_ISENABLE));
                bean.setPosition(cursor.getInt(NewsChannelTable.ID_POSITION));
                list.add(bean);
            }
            cursor.close();
            return list;
        }
    
        public List<NewsChannelBean> queryAll() {
            Cursor cursor = db.query(NewsChannelTable.TABLENAME, null, null, null, null, null, null);
            List<NewsChannelBean> list = new ArrayList<>();
            while (cursor.moveToNext()) {
                NewsChannelBean bean = new NewsChannelBean();
                bean.setChannelId(cursor.getString(NewsChannelTable.ID_ID));
                bean.setChannelName(cursor.getString(NewsChannelTable.ID_NAME));
                bean.setIsEnable(cursor.getInt(NewsChannelTable.ID_ISENABLE));
                bean.setPosition(cursor.getInt(NewsChannelTable.ID_POSITION));
                list.add(bean);
            }
            cursor.close();
            return list;
        }
    
        public void updateAll(List<NewsChannelBean> list) {
        }
    
        public boolean removeAll() {
            int result = db.delete(NewsChannelTable.TABLENAME, null, null);
            return result != -1;
        }
    }
    View Code


    3.BaseListFragment抽象类

    3.1.最底层的接口IBaseView<T>

      不理解泛型==>参考这篇文章理解java泛型的作用。

      

      这里是这样定义这个接口的。 

    public interface IBaseView<T> {
    
        /**
         * 显示加载动画
         */
        void onShowLoading();
    
        /**
         * 隐藏加载
         */
        void onHideLoading();
    
        /**
         * 显示网络错误
         */
        void onShowNetError();
    
        /**
         * 设置 presenter
         */
        void setPresenter(T presenter);
    
        /**
         * 绑定生命周期
         */
        <T> LifecycleTransformer<T> bindToLife();
    }

      定义了5个方法,在实现类中必须要实现的方法(除了抽象类不必全部实现)

    • onShowLoading==>显示加载动画
    • onHideLoading==>隐藏加载动画
    • onShowNetError==>显示网络错误
    • setPresenter==>设置presenter
    • bindToLife==>绑定生命周期

      <T>   LifecycleTransformer<T> bindToLife()这个方法:

        前面的<T> 只是说明这是一个泛型方法。

        返回值是LifecycleTransformer<T> 是RxJava中定义的一个类型。

    3.2.同理看一下IBaseListView<T>

    public interface IBaseListView<T> extends IBaseView<T> {
    
        /**
         * 显示加载动画
         */
        void onShowLoading();
    
        /**
         * 隐藏加载
         */
        void onHideLoading();
    
        /**
         * 显示网络错误
         */
        void onShowNetError();
    
        /**
         * 设置 presenter
         */
        void setPresenter(T presenter);
    
        /**
         * 绑定生命周期
         */
        <T> LifecycleTransformer<T> bindToLife();
    
        /**
         * 设置适配器
         */
        void onSetAdapter(List<?> list);
    
        /**
         * 加载完毕
         */
        void onShowNoMore();
    }

      明白了:接口是可以继承的。

      这里多了两个方法:onSetAdapter(List<?> list)==>顾名思义,这里设置的一定是一个List类型的适配器。

               onShowNoMore()==>这里多了一个加载完毕的方法。

    3.3.IBasePresenter接口类

      这个接口相对于比较简单。

    public interface IBasePresenter {
    
        /**
         * 刷新数据
         */
        void doRefresh();
    
        /**
         * 显示网络错误
         */
        void doShowNetError();
    }
    • doRefresh==>用来刷新数据。
    • doShowNetError==>显示网络错误。

    3.4.抽象类BaseFragment<T extends IBasePresenter>

      T就是就是继承了IBasePresenter接口的一个泛型。

      但是这个BaseFragment中并没有实现接口中的方法,因为它本身也是一个抽象类。方法交给子类去实现吧。

      所以这个抽象类中定义了一些方法。

      这里面又重写3个抽象方法。 

     /**
         * 绑定布局文件
         *
         * @return 布局文件ID
         */
        protected abstract int attachLayoutId();
    
        /**
         * 初始化视图控件
         */
        protected abstract void initView(View view);
    
        /**
         * 初始化数据
         */
        protected abstract void initData() throws NullPointerException;

      attachLayoutId==>可以获取布局文件ID

      initView==>初始化视图控件

      initData==>初始化数据

      然后定义了一个沟通者presenter,采用泛型定义。

      protected T presenter;

      

      当然只实现了一个IBaseView接口方法

        /**
         * 绑定生命周期
         */
        @Override
        public <T> LifecycleTransformer<T> bindToLife() {
            return bindUntilEvent(FragmentEvent.DESTROY);
        }

      所以这就是为什么不把IBaseView中的接口方法全部加到BaseFragment中,而是分了一个抽象类+一个接口。

     

      在BaseFragment抽象类中要进行初始化Toolbar 

     /**
         * 初始化 Toolbar
         */
        protected void initToolBar(Toolbar toolbar, boolean homeAsUpEnabled, String title) {
            ((BaseActivity) getActivity()).initToolBar(toolbar, homeAsUpEnabled, title);
        }

      传入的参数有:标题栏布局,是否有左箭头,title。

      首先执行onCreate==>设置presenter

     @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setPresenter(presenter);
        }

      然后执行onCreateView==>initView(view)==>initData()

     @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 
                                    @Nullable Bundle savedInstanceState) { View view
    = inflater.inflate(attachLayoutId(), container, false); initView(view); initData(); return view; }

      得到fragment视图布局id,初始化视图为抽象函数,在子类中实现。初始化数据为抽象函数,在子类中实现。

    3.5.懒加载碎片LazyLoadFragment<T extends IBasePresenter>

     继承关系: 

    public abstract class LazyLoadFragment<T extends IBasePresenter> extends BaseFragment<T>

      继承了上面刚分析的抽象类,这个也是一个抽象类,不必实现所有的抽象函数。

      先分析三个boolean

      protected boolean isViewInitiated;
       protected boolean isVisibleToUser;
       protected boolean isDataInitiated;
    • isViewInitiated==>视图是否初始化。
    • isVisibleToUser==>是否对用户可见。
    • isDataInitiated==>数据是否初始化完成。

      

      然后执行onCreate 

       @Override
       public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }

      然后执行onActivityCreated

      @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            isViewInitiated = true;
            prepareFetchData();
        }

      确定视图已经初始化了。修改isViewInitiated为true。

      然后执行准备取数据。 

        public boolean prepareFetchData() {
            return prepareFetchData(false);
        }

      然后定义一个带有一个参数(是否强制刷新的变量)的取数据的函数。

     public boolean prepareFetchData(boolean forceUpdate) {
            if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
                fetchData();
                isDataInitiated = true;
                return true;
            }
            return false;
        }

      如果对用户可见、视图初始化完毕、(数据未初始化完毕或强制刷新)==>执行取数据的抽象函数。

      执行定义的抽象函数==>fetchData() 

     public abstract void fetchData();

      最后将数据是否初始化完毕的变量赋值为true。

      

    3.6.抽象函数BaseListFragment<T extends IBasePresenter>

      对于类名称加一个T,或者加一个<T extends ?>的理解

      可以参考一下这篇文章:java中的泛型类及其使用。

      

      

      看一下继承方式。

    public abstract class BaseListFragment<T extends IBasePresenter> 

                extends LazyLoadFragment<T>
        
                implements IBaseListView<T>, SwipeRefreshLayout.OnRefreshListener

      继承了懒加载碎片==>主要抽象函数fetchData()取数据

      实现了IBaseListView<T>==>主要有7个抽象方法。

      实现了SwipeRefreshLayout.OnRefreshListener==>主要方法onRefresh()刷新数据

      #调用了开源框架==>Multitype框架==>处理复杂的视图列表

      github地址:https://github.com/drakeet/MultiType

      

      预览一下成员变量。

      public static final String TAG = "BaseListFragment";
        protected RecyclerView recyclerView;
        protected SwipeRefreshLayout swipeRefreshLayout;
        protected MultiTypeAdapter adapter;
        protected Items oldItems = new Items();
        protected boolean canLoadMore = false;
        protected Observable<Integer> observable;

      这里的MultiTypeAdapter+Items 都是第三方库框架,方便处理复杂的视图。

      这里的Observable<Integer>是RxJava框架。

      实现了BaseFragment中的抽象函数==>attachLayoutId()绑定布局文件。 

      @Override
        protected int attachLayoutId() {
            return R.layout.fragment_list;
        }

      这个布局文件是自己早就写好了的。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        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="match_parent"
        android:background="@color/windowBackground"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fadeScrollbars="true"
                android:scrollbarFadeDuration="1"
                android:scrollbars="vertical"
                app:layoutManager="LinearLayoutManager">
            </android.support.v7.widget.RecyclerView>
    
        </android.support.v4.widget.SwipeRefreshLayout>
    
    </LinearLayout>

      预览效果如下:

      

      初始化视图initView(View view) 

     @Override
        protected void initView(View view) {
            recyclerView = view.findViewById(R.id.recycler_view);
            recyclerView.setHasFixedSize(true);
    
            swipeRefreshLayout = view.findViewById(R.id.refresh_layout);
            swipeRefreshLayout.setColorSchemeColors(SettingUtil.getInstance().getColor());
            swipeRefreshLayout.setOnRefreshListener(this);
        }

      recyclerView是列表。

      swipeRefreshLayout是包裹列表的一个可以刷新的布局。

      实现IBaseView<T>接口中的显示加载动画+隐藏加载动画函数。

     @Override
        public void onShowLoading() {
            swipeRefreshLayout.post(new Runnable() {
                @Override
                public void run() {
                    swipeRefreshLayout.setRefreshing(true);
                }
            });
        }
    
        @Override
        public void onHideLoading() {
            swipeRefreshLayout.post(new Runnable() {
                @Override
                public void run() {
                    swipeRefreshLayout.setRefreshing(false);
                }
            });
        }

      实现懒加载碎片中的抽象函数==>fetchData()来取数据。

     @Override
        public void fetchData() {
            observable = RxBus.getInstance().register(BaseListFragment.TAG);
            observable.subscribe(new Consumer<Integer>() {
                @Override
                public void accept(@NonNull Integer integer) throws Exception {
                    adapter.notifyDataSetChanged();
                }
            });
        }

      这里进行订阅。拿到数据,然后刷新。

      

      实现IBaseView<T>中的接口函数onShowNetError。 

     @Override
        public void onShowNetError() {
            Toast.makeText(getActivity(), R.string.network_error, Toast.LENGTH_SHORT).show();
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    adapter.setItems(new Items());
                    adapter.notifyDataSetChanged();
                    canLoadMore = false;
                }
            });
        }

      这里提示用户,网络不给力。

      生命周期函数onResume。 

    @Override
        public void onResume() {
            super.onResume();
            // 设置下拉刷新的按钮的颜色
            swipeRefreshLayout.setColorSchemeColors(SettingUtil.getInstance().getColor());
        }

      

      实现IBaseListView额外的两个函数中的一个onShowNoMore加载完毕接口函数。

    @Override
        public void onShowNoMore() {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (oldItems.size() > 0) {
                        Items newItems = new Items(oldItems);
                        newItems.remove(newItems.size() - 1);
                        newItems.add(new LoadingEndBean());
                        adapter.setItems(newItems);
                        adapter.notifyDataSetChanged();
                    } else if (oldItems.size() == 0) {
                        oldItems.add(new LoadingEndBean());
                        adapter.setItems(oldItems);
                        adapter.notifyDataSetChanged();
                    }
                    canLoadMore = false;
                }
            });
        }

      实现刷新框的onRefresh 

    @Override
        public void onRefresh() {
            int firstVisibleItemPosition = ((LinearLayoutManager) 
                    recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
    if (firstVisibleItemPosition == 0) { presenter.doRefresh(); return; } recyclerView.scrollToPosition(5); recyclerView.smoothScrollToPosition(0); }

      先判断第一次可见的位置在最前面,也就是一开始没有数据的时候。

      如果是第一次点进去的话,必然要执行刷新。

      然后recyclerView回到顶部。

      最后执行一个onDestroy来反注册。

        @Override
        public void onDestroy() {
            RxBus.getInstance().unregister(BaseListFragment.TAG, observable);
            super.onDestroy();
        }
      有个非常非常坑爹的地方 
      RxFragment有两个类型:
      
    import com.trello.rxlifecycle2.components.RxFragment;
    import com.trello.rxlifecycle2.components.support.RxFragment;

      RxFragment==>类型是app.Fragment

        support.RxFragment==>类型是v4.app.Fragment 是兼容的。


    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    Qt 错误汇集贴
    转:Qt编写串口通信程序全程图文讲解
    转:QT 的点点滴滴 错误总结
    转:Qt项目中遇到的一些小问题汇总
    转:AM335X 启动流程
    基于Xilinx Zynq的计算处理平台
    基于英伟达Jetson TX1的GPU处理平台
    基于6U VPX的 SRIO 接口, 和PCIe 接口的msata 固态存储卡
    国芯网 邀请国产芯片原厂入驻商城
    295-Xilinx Kintex-7 X7K325T的半高PCIe x4双路万兆光纤收发卡
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/7985495.html
Copyright © 2011-2022 走看看