RxJava終章之實踐出真知(七)

LeiHolmes發表於2017-10-31

前言

  到本階段,相信各位碼友對RxJava的原理及操作符的使用方法已經基本掌握了。只是瞭解理論知識對於我們們程式猴來說當然遠遠不夠,理論運用到實踐才能出真知。一起來律動指尖到實際場景中看看怎麼運用RxJava。本篇我們演示一下如何運用RxJava從手機中獲取已安裝的第三方應用並通過RecyclerView展示出來。
  

準備工作

專案下build.gradle

buildscript {
    ......
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.2'
        //ButterKnife支援
        classpath 'com.jakewharton:butterknife-gradle-plugin:8.7.0'
    }
}
......複製程式碼

app下build.gradle

apply plugin: 'com.android.application'
apply plugin: 'com.jakewharton.butterknife'

android {
    ......
    defaultConfig {
        ......
        //Lambda支援
        jackOptions {
            enabled true
        }
    }
    //Lambda支援
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    ......
}

dependencies {
    ......
    //RxJava支援
    compile 'io.reactivex:rxjava:1.0.14'
    compile 'io.reactivex:rxandroid:1.0.1'
    //RecyclerView支援
    compile 'com.android.support:recyclerview-v7:25.3.1'
    //ButterKnife支援
    compile 'com.jakewharton:butterknife:8.7.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0'
}複製程式碼

佈局

主佈局

  主佈局沒啥好說的,就是一個RecyclerView。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.holmeslei.rxjavademo.ui.PracticeActivity>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_app_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>複製程式碼

RecyclerView的Item佈局

  RecyclerView單個條目佈局,我們需要一個ImageView及一個TextView用來展示每個應用的圖示及名稱。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:paddingLeft="20dp">

        <ImageView
            android:id="@+id/item_iv_head"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_centerVertical="true"
            android:scaleType="fitXY"
            android:src="@mipmap/ic_launcher" />

        <TextView
            android:id="@+id/item_iv_app_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="30dp"
            android:layout_toRightOf="@+id/item_iv_head"
            android:text="微信"
            android:textColor="#555555"
            android:textSize="18sp" />
    </RelativeLayout>
</RelativeLayout>複製程式碼

Java程式碼

實體類

public class AppInfo {
    private String appName; //應用名稱
    private Drawable appIcon; //應用圖示

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public Drawable getAppIcon() {
        return appIcon;
    }

    public void setAppIcon(Drawable appIcon) {
        this.appIcon = appIcon;
    }

    @Override
    public String toString() {
        return "AppInfo{" +
                "appName='" + appName + '\'' +
                ", appIcon=" + appIcon +
                '}';
    }
}複製程式碼

RecyclerView介面卡

public class AppInfoListAdapter extends RecyclerView.Adapter<AppInfoListAdapter.MyViewHolder> {
    private Context context;
    private List<AppInfo> appInfoList;

    public AppInfoListAdapter(Context context, List<AppInfo> appInfoList) {
        this.context = context;
        this.appInfoList = appInfoList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_app_list, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        //設定應用圖示
        holder.ivHead.setImageDrawable(appInfoList.get(position).getAppIcon());
        //設定應用名稱
        holder.tvAppName.setText(appInfoList.get(position).getAppName());
    }

    @Override
    public int getItemCount() {
        return appInfoList.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.item_iv_head)
        ImageView ivHead;
        @BindView(R.id.item_iv_app_name)
        TextView tvAppName;

        MyViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}複製程式碼

Activity

  核心來了,我們重點看initData()方法。分析一下需求,要獲取手機中已安裝的第三方應用並展示出來,主要分以下幾步:

  1. 從系統中獲取所有應用列表資料的集合List<ApplicationInfo>
  2. 這個集合是已安裝的所有應用集合,我們只需要其中的第三方應用,所以要使用到RxJava的filter操作符進行過濾。
  3. 由於ApplicationInfo不滿足我們的需求,需要將其轉換為我們自定義的實體類AppInfo,所以要使用到RxJava的map操作符進行轉換。
  4. 由於獲取應用集合,過濾,轉換的過程可能是耗時的,我們需要指定Observable執行在io執行緒。
  5. 由於獲取到滿足條件的資料後我們還需重新整理UI進行展示,所以還需要指定Observer執行在Android的UI執行緒。
  6. 最後還需要輸出錯誤日誌,及完成之後的重新整理UI,所以需要重寫RxJava錯誤狀態及完成狀態的回撥方法。
  
  瞭解的整個實現流程,接下來上程式碼:

public class PracticeActivity extends AppCompatActivity {
    @BindView(R.id.rv_app_list)
    RecyclerView rvAppList;

    private AppInfoListAdapter adapter;
    private List<AppInfo> appInfoList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_practice);
        ButterKnife.bind(this);
        initRecyclerView();
        initData();
    }

    /**
     * 初始化RecyclerView
     */
    private void initRecyclerView() {
        LinearLayoutManager manager = new LinearLayoutManager(this);
        rvAppList.setLayoutManager(manager);
        adapter = new AppInfoListAdapter(this, appInfoList);
        rvAppList.setAdapter(adapter);
    }

    /**
     * 初始化資料
     */
    private void initData() {
        final PackageManager pm = getPackageManager();
        //獲取所有應用資訊集合
        List<ApplicationInfo> infoList = pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
        Observable.from(infoList)
        //過濾出已安裝的第三方應用
        .filter(new Func1<ApplicationInfo, Boolean>() {
            @Override
            public Boolean call(ApplicationInfo applicationInfo) {
                return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) <= 0;
            }
        })
        //轉換為自定義的AppInfo類
        .map(new Func1<ApplicationInfo, AppInfo>() {
            @Override
            public AppInfo call(ApplicationInfo applicationInfo) {
                AppInfo appInfo = new AppInfo();
                appInfo.setAppIcon(applicationInfo.loadIcon(pm));
                appInfo.setAppName(applicationInfo.loadLabel(pm).toString());
                return appInfo;
            }
        })
        //Observable被觀察者執行在io執行緒
        .subscribeOn(Schedulers.io())
        //Observer觀察者執行在AndroidUI執行緒
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<AppInfo>() {
            @Override
            public void onCompleted() {
                //更新列表UI
                adapter.notifyDataSetChanged();
            }

            @Override
            public void onError(Throwable e) {
                //顯示錯誤資訊
                Toast.makeText(PracticeActivity.this, e.getMessage(), 
                        Toast.LENGTH_LONG).show();
            }

            @Override
            public void onNext(AppInfo appInfo) {
                //新增第三方應用資料到集合
                appInfoList.add(appInfo);
            }
        });
}複製程式碼

Lambda簡化

  還記得我在RxJava系列第一篇中提到過嗎?RxJava可結合Lambda表示式達到簡化程式碼的作用,來看一下簡化之後的程式碼:

/**
 * 初始化資料
 */
 private void initData() {
    final PackageManager pm = getPackageManager();
    List<ApplicationInfo> infoList = pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
    Observable.from(infoList)
            .filter(applicationInfo -> (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) <= 0)
            .map(applicationInfo -> {
                AppInfo appInfo = new AppInfo();
                appInfo.setAppIcon(applicationInfo.loadIcon(pm));
                appInfo.setAppName(applicationInfo.loadLabel(pm).toString());
                return appInfo;
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    appInfo -> appInfoList.add(appInfo),
                    throwable -> Toast.makeText(PracticeActivity.this, throwable.getMessage(), Toast.LENGTH_LONG).show(),
                    () -> adapter.notifyDataSetChanged()
            );
}複製程式碼

  是不是看起來清爽簡潔呢?不太瞭解Lambda表示式的碼友可跳轉到我的另一篇講解Lambda表示式的文章:
  Lambda表示式基本語法與應用

執行效果

總結

  到此,RxJava系列從理論到運用再到實踐,整個過程我們通過了7篇文章來學習。然而RxJava的知識遠遠不止這些,這就需要各位碼友去探索發掘了。本系列只是達到入門RxJava的程度,且是基於RxJava1.0版本進行講解的。目前RxJava已經更新到了2.0+,與1.0版本也有不小的改動與優化的地方。後期我會專門對RxJava2.x有何改動開一篇文章進行講解,敬請期待。
  技術渣一枚,有寫的不對的地方歡迎大神們留言指正,有什麼疑惑或者建議也可以在我Github上RxJavaDemo專案Issues中提出,我會及時回覆。
  附上RxJavaDemo的地址:
  RxJavaDemo

相關文章