2016年12月13日 星期二

Android + Node.js with Volley(排球?) 簡單RESTFul API 實作

首先我要來講一位滿懷熱血,想要打倒任何人,身高1米7卻想要違反地心引力,在空中的爭奪戰不想輸給任何一位....痾大家不要轉台。現在開始正題
其實網路上很多大大們分享,其實我也使用一段時間了,用於下載網路圖片或是簡單的網路連線,那會寫這篇是想連同上一篇寫一個RESTFul API 讓手機得到想要的資料!

介紹

由於HttpURLConnection和HttpClient用法過於複雜, 如果沒有適當的封裝, 容易寫出重覆的程式碼,因此Google推出Volley Http request framework來處理簡單的HTTP Request, 另外也可以下載圖片, volley是屬於輕量級的HTTP Request處理工具, 因此對於大量數據處理, 例如下載文件, 效能就沒有那麼理想。
使用

gradle
compile 'com.mcxiaoke.volley:library:1.0.19'
權限(Permission)因為要使用網路連線
<uses-permission android:name="android.permission.INTERNET" />
程式碼(Code)

首先取得Volley 的 RequestQueue物件
RequestQueue mQueue = Volley.newRequestQueue(this);
**注意** 
RequestQueue是一個請求隊列對象,可以緩存所有的HTTP請求,然後按照一定的算法並發地發出這些請求。不必為每一次HTTP請求都創建一個RequestQueue對象,這是非常浪費資源的,基本上在每一個需要和網絡交互的Activity中創建一個RequestQueue對象就足夠了。建議將mQueue設為單一物件全域使用,避免浪費資源。

為了發出Http 請求,還需要StringRequest物件
StringRequest stringRequest = new StringRequest("http://192.168.0.13:8081/listUsers",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("volley",response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d("volley",error.getMessage(), error);
}
});
StringRequest的函數需要傳入三個參數,第一個參數就是URL地址,第二個參數是服務器響應成功的callback,第三個參數是服務器響應失敗的callback。
**注意** http://192.168.0.13 為區網IP,每台電腦不一樣,可由CMD 打上 ipconfig 查看

最後,將這個StringRequest對象添加到RequestQueue裡面就可以了,如下所示:

mQueue.add(stringRequest);
手機的部分就差不多了,那我們來看"http://192.168.0.13:8081/listUsers"這支RESTFul API 如何寫

如果大家還沒裝Node.js 以及 express 可以看Node.js + Express 基本認識 in windows

這裡我要做簡單的網路連線跟server要資料(檔案裡的內容)
首先開啟記事本並記得改檔名(account.json),並打上以下json格式
[{"name":"andy","pass":"08160816"}]
這份就是client跟server要資料回傳的內容

重頭戲來了
接下來一樣打開記事本並記得改檔名(workserver.js),並打上以下程式碼

var express = require('express');
var app = express();
var fs = require("fs");

//添加的新用户数据
var user = {
    "name" : "yuyu",
 "pass" : "password1"
}

app.get('/addUser', function (req, res) {
   fs.readFile( __dirname + "/" + "account.json", 'utf8', function (err, data) {
       data = JSON.parse(data.toString().trim());
       data.push(user);
       console.log( data );
    fs.writeFile(__dirname + "/" + "account.json", JSON.stringify(data), 'utf8', function(err) {
    if (err) throw err;
    console.log('complete');
    });
       res.end( JSON.stringify(data));
   });
   
})

app.get('/listUsers', function (req, res) {
   fs.readFile( __dirname + "/" + "account.json", 'utf8', function (err, data) {
       console.log( data );
       res.end( data );
   });
})

var server = app.listen(8081, function () {

  var host = server.address().address
  var port = server.address().port

  console.log("IP http://%s:%s", host, port)

})
有注意到 app.get('/listUsers'.....這段嗎?解釋為:當對localhost:8081/listUsers發出GET請求,Server回復account.json檔案內容

接下來就打開node.js cmd ,切換到檔案的位置並執行
node workserver.js
這裡以網頁顯示這支RESTFul API 的回傳結果

**注意**  
若是想要在自己電腦並用手機測試,記得兩個裝置(電腦、手機)需是同網段(連到同個wifi),並用區網連線的方式就可以進行連線。若是自己有實體IP就沒這個問題!


以上就是簡單Server 與 Client 端 連線,寫出一個可以進行溝通的程式,若有什麼問題可以互相討論,之後有空會出一篇有關使用Volley來下載網路圖片!

Refrence:
http://blog.csdn.net/guolin_blog/article/details/17482095
https://bng86.gitbooks.io/android-third-party-/content/volley.html













2016年12月8日 星期四

Node.js + Express 基本認識 in windows

Node.js 在我大學時就聽過,也距離3、4年前了,因為工作上需求開始了解Node.js,並與我比較掌握的Android有所連接。(PS不想看簡介與優點請直接下拉到安裝與教學)

簡介

Node.js 是 Ryan Dahl 基於 Google 的 V8 引擎於 2009 年釋出的一個 JavaScript 開發平台,主要聚焦於 Web 程式的開發,通常用被來寫網站。以往大家都學過PHP、Python 和 Ruby 等的腳本語言一樣,需要有 Web Server 才能寫出 Web 程式,Node.js 透過模組的支援,Node.js 自己就可以寫出Web Server並且可以控制它的行為與運作方式,甚至還可以針對不同的功能用不同Port來建立多個服務,看起來是不是不錯,接下來繼續講Node.js的優點。

優點

高效能
因為 Node.js 採用「非同步式 I/O」的方式,所以,不會因為硬體設備的延遲而造成程式因為需要等待回應而影響到程式的執行,因此,執行速度較常見的同步式來的快。

低耗能
除了非同步式I/O之外,Node.js 還採用 Evented I/O 的模式,因此,Node.js 不需要像一般的 Web Server 需要一大塊記憶體來放著用,而Node.js 只在需要時才會去使用必要的資源,相對比起來較省資源。

入門容易
 JavaScript 是一套可深可淺的語言,就是,不需要很懂也可以寫出想要的東西,但是,如果很懂就可以寫出很多令人不可思議的應用(OOP),而 Node.js 因為採用 Javascript ,因此,對於 Web 程式開發者來說,幾乎是無痛的學會另一種應用哩 !

社群支援強
Node.js 有一個叫做 NPM 的網站,專門讓人來提供自己開發的套件 (Package),目前在 NPM 上面已經有 5000 多個套件,所以,可以容易找到需要的功能來用,不需要自己重頭寫,可以加快開發的速度哩 !

安裝與教學

首先到官網安裝Node.js,安裝過程沒有甚麼特別的,接下來你會看到兩個Node.js、 Node.js Commad Prompt


接下來我們就使用Node.js Commad Prompt來寫一隻Hello World!來吧
首先在Node.js的目錄下新增一個文字文件來(*註 若要顯示中文請選擇UTF8),在內文中打上
console.log("Hello World!");
接下來改檔名-->"hello.js"`,並在Node.js cmd 打上
node hello.js


接下來第二個重頭戲
我們來建立一個web,在網頁上顯示Hello World!
首先跟上面一樣的步驟,內文打上
var http = require("http");
http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}).listen(8888);
1.使用require指令來載入http的模組
2.創建伺服器:使用http.createServer()方法建立伺服器,並使用listen(8888)綁定8888port,函數通過request、reponse來接收和響應數據
3.啟動:在Node,js cmd 打上 node hello.js
這樣我們就完成了第一個Node.js基本階段,下一階段開始使用套件加速開發網頁

Express 介紹、安裝與教學

Express是目前最穩定、使用最廣泛開發框架,並且是Node.js官方唯一推薦的Web開發框架。BYVoid在Node.js開發指南中提到
Express除了為HTTP模組提供了更高階的接口外,還實現了許多功能,其中包含:路由控制、模板解析支持、動態視圖、用戶會話、CSRF保護、靜態文件服務、錯誤控制器、訪問日誌、緩存、插件支持。
特別在此註明,Express不是一個無所不包的全能框架,像Rails或Django的那樣實現了模板引擎甚至ORM (Object Relation Model,對象關係模型),它只是一個輕量級的框架,多數功能只是對HTTP協議中常用操作的封裝,更多的功能需要插件或整合其他模組來完成。
首先打開我們的Node.js cmd 輸入
npm install express --save 
以上命令會將 Express 框架安裝在當前目錄的 node_modules 目錄中, node_modules 目錄下會自動創建 express 目錄。以下幾個重要的模塊是需要與 express 框架一起安裝的:
body-parser:用於處理 JSON, Raw, Text 和 URL 編碼的數據。
cookie-parser : 這就是一個解析Cookie的工具。通過req.cookies可以取到傳過來的cookie,並把它們轉成對象。
multer :用於處理 enctype="multipart/form-data"(設置表單的MIME編碼)的表單數據。
npm install body-parser --save
npm install cookie-parser --save npm install multer --save
那接下來我們來用Express 寫 Hello World
var http = require('http');
var express = require('express');
var app = express();
var server = http.createServer(app);
app.get('/',function(request, response){ //我們要處理URL為 "/" 的HTTP GET請求
    response.send('Hello World'); //作出回應
});
server.listen(8080,'127.0.0.1',function(){
    console.log('HTTP in http://127.0.0.1:8080/ run');
});
這裡大家應該對下面有所疑問
app.get('/', function (req, res) {
   // --
})
以下介紹routing(路由) 的概念

路由是指判斷應用程式如何回應用戶端對特定端點的要求,而這個特定端點是一個 URI(或路徑)與一個特定的 HTTP 要求方法(GET、POST 等)。

每一個路由可以有一或多個處理程式函數,當路由相符時,就會執行這些函數。

路由定義的結構如下:
app.METHOD(PATH, HANDLER)
其中:
  • app 是 express 的實例。
  • METHOD 是 HTTP 要求方法
  • PATH 是伺服器上的路徑。
  • HANDLER 是當路由相符時要執行的函數。
讓我們回到上面
app.get('/',function(request, response){ //我們要處理URL為 "/" 的HTTP GET請求
    response.send('Hello World'); //作出回應
});
其解釋為:對根目錄('/')發出GET要求時並回應"Hello World"
就這樣完成了Express 的 Hello World

看完這篇大致上會對於Node.js 跟 Express 有些基本認識,下次會介紹Express的框架並實做,感謝大家,Node,js 完全新手若有任何問題歡迎一起討論與學習。

Refrence:
http://expressjs.com/zh-tw/starter/basic-routing.html
http://www.runoob.com/nodejs/nodejs-express-framework.html
https://blog.allenchou.cc/nodejs-tuts-2-using-express-framework/
http://www.runoob.com/nodejs/nodejs-http-server.html




2016年11月26日 星期六

Android RecyclerView Introduce & Implement 介紹&實作

大部分的開發者對於ListView應該不陌生吧,但是今天要來介紹的是RecyclerView,Android是一個不斷進化的平台,Android 5.0的v7版本支持包中引入了新的RecyclerView控制項,正如官方文檔所言,RecyclerView是ListView的豪華增強版。它主要包含以下幾處新的特性,如ViewHolder,ItemDecorator,LayoutManager,SmothScroller以及增加或刪除item時item動畫等。官方推薦我們採用RecyclerView來取代ListView。

優點:



1) ViewHolder Pattern

在很多的ListView 範例中,常常會看到ViewHolder的使用,因為在ListView中有時候會大量的加載View,這樣會造成記憶體浪費,影響性能,這時候就可以使用ViewHolder 靜態類別方法的設計方法,避免造成過多的記憶體浪費,那在RecylerView 中強制要使用ViewHolder,雖然可能會變得比較複雜一點,但是我們在ListView中所遇到的一些問題都被更有效率的解決!
可以參考這篇ViewHolder用法
2) LayoutManager

ListView只提供垂直的上下滑動,不能水平滑動等,現在使用RecylerView提供了3種
i.   LinearLayoutManager         支援水平或垂直的清單(lists)
ii.  StaggeredLayoutManager  支援拼貼樣式像是交錯的清單
iii. GridLayoutManager            支援呈現網格像是圖集的清單
最好的一件事是我們可以動態地做我們想要呈現的layout
3) Item Animator
LisrView 缺少好的動畫,但是RecyclerView 帶來全新的動畫方式,藉由RecyclerView.ItemAnimator 類別,動畫的呈現變得更簡單於直觀,可以在添加刪除或移動項目時做動畫

4) Item Decoration
在ListView中,動態地裝飾項目像是增加界線或是分隔線都是不容易的,但是在RecyclerView的 RecyclerView.ItemDecorator 類別給予開發者很大的控制彈性,但卻也會造成更多的時間浪費與複雜
5) OnItemTouchListener
在ListView中點擊項目的動作是很簡單的,多虧有了 AdapterView.OnItemClickListenerinterface,
而RecyclerView 藉由RecyclerView.OnItemTouchListener給予開發者更強的控制,卻也變得比較複雜

介紹完我就在下面簡單實作RecyclerView

1:Gradle配置 build.gradle

compile 'com.android.support:recyclerview-v7:23.0.0'

2:建立Main Layout activity_recyclerview.xml



<?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"    
  xmlns:tools="http://schemas.android.com/tools"    
  android:id="@+id/activity_main"    
  android:layout_width="match_parent"    
  android:layout_height="match_parent"    
  tools:context="com.example.yi_an.myrecyleview.MainActivity">
    <android.support.v7.widget.RecyclerView        
      android:id="@+id/myrecycle"        
      android:layout_width="match_parent"        
      android:layout_height="match_parent"
     />

</LinearLayout>
由於測試需要多種item Layout的加載,需要建立2個item layout
3:建立Item 1 Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
 android:orientation="vertical" 
 android:layout_width="match_parent"    
 android:layout_height="match_parent">
    <TextView        
       android:text="TextView"        
       android:layout_width="match_parent"        
       android:layout_height="wrap_content"        
       android:textSize="20sp"        
       android:id="@+id/textView" />
</LinearLayout>

4:建立Item 2 Layout

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
  android:orientation="vertical" 
  android:layout_width="match_parent"    
  android:layout_height="match_parent">
    <com.example.yi_an.myrecyleview.MySpinner        
        android:id="@+id/button"        
        android:layout_height="wrap_content"        
        android:layout_width="wrap_content"        
        android:textSize="20sp"        
     />
</LinearLayout>



5:建立Activity跟RecyclerView Adapter
RecyclerView.Adapter 負責提供 child-view 給 RecyclerView 使用,
同時也負責把資料跟 child-view 綁在一起。實際上並不是直接對上 View,
而是對上 ViewHolder。從設計上來看就是強迫使用 ViewHolder pattern 來增加效率。
此抽象類別需要實作這四個 method
  • getItemCount() - Adapter才知道如何儲存資料及回傳有多少筆資料
  • onCreateViewHolder(ViewGroup parent, int viewtype) - 現有的 ViewHolder不夠用,要求Adapter產生一個新的viewholder,也可以根據viewtype產生你要的viewholder(對應第四個function)
  • onBindViewHolder(ViewHolder viewholder, int position) - 重用之前產生的 ViewHolder,把特定位置的資料連結上去準備顯示
  • getItemViewType(int) - 根據位置回傳你要View的樣式
 public class MainActivity extends AppCompatActivity{
    private RecyclerView mRecyclerView;
    private RecyclerView.LayoutManager mLayoutManager;
    private MyAdapter mAdapter;
    private String [] myDataset;
    private List<MyItem> myItemList;
    private String [] selectdata;
    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myDataset  = new String[]{"test","test1","test3","4","5","6","7","8"};
        myItemList = new ArrayList<>();
        MyItem myItem1 = new MyItem("1","select","1/2/3/4/5/6");
        MyItem myItem2 = new MyItem("2","text",null);
       
        myItemList.add(myItem1);
        myItemList.add(myItem2);
       
        selectdata = new String[myItemList.size()];
        mRecyclerView = (RecyclerView)findViewById(R.id.myrecycle);
        // use this setting to improve performance if you know that changes        // in content do not change the layout size of the RecyclerView        mRecyclerView.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        // specify an adapter (see also next example)        mAdapter = new MyAdapter(this,myItemList);
        mRecyclerView.setAdapter(mAdapter);
    }
}  
private class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
        private  String[] mdataset;
        private  List<MyItem> list;
        private  LayoutInflater  mLayoutInflater;

        public class ViewHolder extends RecyclerView.ViewHolder {
            public MySpinner mySpinner;
            public ViewHolder(View v) {
                super(v);
                mySpinner = (MySpinner)v.findViewById(R.id.button);
            }
        }

        public class ViewHolder1 extends RecyclerView.ViewHolder {
            public TextView mTextView;
            public ViewHolder1(View v) {
                super(v);
                mTextView = (TextView) v.findViewById(R.id.textView);

            }
        }

        public MyAdapter(Context context, List<MyItem> myItem ){           
            list = myItem;
            mLayoutInflater = LayoutInflater.from(context);
        }


        @Override        
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            Log.d("recycle","onCreateViewHolder");
            if(viewType==0) {
                View v = mLayoutInflater.inflate(R.layout.my_spinner, parent, false);
                return new ViewHolder(v);
            }else {
                View v = mLayoutInflater.inflate(R.layout.my_text_view, parent, false);
                return new ViewHolder1(v);
            }
        }

        @Override        
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            Log.d("recycle","onBindViewHolder");
                Log.d("position",position+"");
                if(holder instanceof ViewHolder){
                    ((ViewHolder) holder).mySpinner.initContent(MainActivity.this,list.get(position).value.split("/"),position);
                    if(selectdata[position]!=null){
                        ((ViewHolder) holder).mySpinner.setText(selectdata[position],null);
                    }
                    for(int x = 0; x<selectdata.length;x++){
                        if(selectdata[x]!=null)
                            Log.d("selectdata",selectdata[x]);
                    }
                }else if(holder instanceof ViewHolder1){
                    ((ViewHolder1) holder).mTextView.setText(list.get(position).value);
                }
        }

        @Override        
        public int getItemViewType(int position) {
            Log.d("recycle","getItemViewType");
            if("select".equals(list.get(position).type)){
                return 0;
            }else{
                return 1;
            }
        }

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


    }
附上gitgub
https://github.com/andy086912597/MyRecyclerView/tree/master
歡迎大家來討論



(References)
http://stackoverflow.com/questions/28525112/android-recyclerview-vs-listview-with-viewholder
https://read01.com/kE4z2.html


2016年11月1日 星期二

MPChart 折線圖 (多個折線)

很多人想要在App上加入一些數據分析圖表,光想要怎麼做就很頭痛八,感謝神人推出MPChart套件可以使用,就讓我們一起來看吧!

先來介紹MPChart 是什麼?

官網:

MPAndroidChart is a powerful & easy to use chart library for Android. It runs on API level 8 and upwards.
As an additional feature, this library allows cross-platform development between Android and iOS as an iOS version of this library is also available
簡單來說它就是一個"圖表"Libraryandroid ios使用。
Wow,那不就很強大,對於想要在視覺化呈現你的數字、資料,我想這個真的必會!

優點:

八種不同的圖表類型

  1.  縮放XY(藉由手勢觸碰、滑動)
  2.  組合圖表
  3. 可訂制XY軸的值
  4.  圖表儲存
  5.  動畫產生值
  6. 可訂製的圖表(字體、顏色、背景、手勢、圖點)
  7.  會隨著你的值縮放圖表

圖表類型:
  1.     長條圖
  2.     折線圖
  3.     圓餅圖
  4.    散點圖
  5.    泡泡圖
  6.    直方圖
  7.    雷達圖
  8.    組合圖

其實官方的網站都有講到這裡附上,那接下來就來實作我們的折線圖

一、首先,在你的專案加入此Lib
        build.gradle裡加入
    dependencies {compile 'com.github.PhilJay:MPAndroidChart:v3.0.0-beta1'}

二、開始在你的layout加入CustomerView,這裡我們要做折線圖
    <com.github.mikephil.charting.charts.LineChart
    android:id="@+id/chart"   
android:layout_width="match_parent"    android:layout_height="match_parent"   
android:layout_margin="10dp" />

三、Activiy定義、設置圖表style、設置圖表功能、設置圖表值並實作出來

定義:LineChart mchart = (LineChart)findViewById(R.id.chart);

設置圖表style
        mChart.setDescription(mDataSource.getDescription());//chart上的右下角加描述
        mChart.setDescriptionTextSize(30);
        mChart.setUnit("%"); //設置Y轴上的单位
        mChart.setAlpha(0.8f);//設置透明度
        mChart.setBorderColor(Color.rgb(213, 216, 214));//設置網格底下的那條線的颜色
        mChart.setBackgroundColor(Color.rgb(255, 255, 255));//設置背景顏色

設置圖表功能:
        mChart.setHighlightEnabled(true);//設置焦點顯示        
         mChart.setTouchEnabled(true);//設置是否可以觸摸,如為false,則不能拖動,縮放等
        mChart.setDragEnabled(true);//設置是否可以拖拽,缩放
        mChart.setScaleEnabled(true);//設置是否在圖點上顯示值
        mChart.setPinchZoom(true); //設置是否能變大變小

設置圖表值:
     我想這個是大家最想知道的
     首先我利用ArrayList去存我的XY(橫、縱軸)

     List<Entry> entries = new ArrayList<>();
{
    x = {1,2,3};
    y = {3,2,1}
    for (int i = 0; i < x.size(); ++i) {
        Entry e = new Entry(x[i], y[i]);
        entries.add(e);
    }
}
LineDataSet dataSet = new LineDataSet(entries, "");
//折線的顏色
dataSet.setColor(Color.GRAY);
dataSet.setLineWidth(3.0f);
//線下方填滿
dataSet.setDrawFilled(true);
dataSet.setFillColor(Color.parseColor("#FFFD464E"));
//圈圈外圍大小跟顏色
dataSet.setDrawCircles(true);
dataSet.setCircleColor(Color.WHITE);
dataSet.setCircleRadius(8.0f);
//圈圈裡面洞大小跟顏色
dataSet.setDrawCircleHole(true);
dataSet.setCircleColorHole(Color.GRAY);
dataSet.setCircleHoleRadius(4.0f);
//要不要在點上面顯示數值
dataSet.setDrawValues(false);
     注意的是這裡的EntryMPChart libclass Entry(float x, float y)
     ,所以要記得把XY轉型成 Float

最後就把數值實作在MPChart裡,就大功告成
  mchart.setData(lineData);

最後教大家怎麼在一張圖放置多個折線

還記得我一開始教大家怎麼存取我們的XY值嗎?!

    List<Entry> entries = new ArrayList<>();
{
    x = {1,2,3};
    y = {3,2,1}
    for (int i = 0; i < x.size(); ++i) {
        Entry e = new Entry(x[i], y[i]);
        entries.add(e);
    }
LineDataSet dataSet = new LineDataSet(entries, "");

那我們現在要創建一個LineDataSetArrayList去存取我們多個折線的值

ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
dataSets.add(dataSet);
dataSets.add(dataSet2);
dataSets.add(dataSet3);
....以此類推
這樣就可以放多個折線

今天就到一段落,這裡不放Code,網路上很多,如果有問題也可以互相切搓


       
   
   





2016年7月23日 星期六

Android MVP 基本了解與實作

這篇文章適合初學者想要了解MVP pattern到底是什麼(因為講得很白話)以及如何實作(簡單登入APP)!!

最一開始大部分的人寫android,都會在Activity寫很多function或是邏輯判斷,頂多用到資料庫會新建類別在建構它使用,最後導致Activity又臭又長,其實學習MVP不只是為了讓架構更清楚了解,其實最大的好處是方便測試和移植,為什麼呢等等最後會解釋


一.MVP(Model-View-Presenter)介紹:

很多人聽過MVC,其實MVP是從MVC衍伸出來的,為了更細分View與Model的功能,讓View處理UI的資料數據變化,這裡不多說MVC是什麼,在MVP模式裡通常包含4個要素:

1. View : 負責繪製UI元素,簡單來說就是Activity以及Fragment。
2. View interface : 需要View的實現接口,View 通過View interface 與 Presenter進行交互,降低耦合,方便進行單元測試。
3. Model : 負責儲存、搜尋、操作資料數據(有時候也會實現一個Model interface用來降低耦合)。
4. Presenter : 作為View與Model交互的橋樑,處理與用戶對應的邏輯功能。

最主要的是MVP都需要透過Presenter當作橋梁去溝通彼此都不認識的View以及Model

二.為甚麼要使用MVP模式

並不是說你用了MVP你的程式碼就更好維護或是更清楚,如果自己的思路很清楚也當然不必使用,但是使用這種架構是可以讓別人比較容易懂得你的程式碼,而最主要的核心思想其實是單元測試(Unit Test),回想一下你在開發Android應用是如何對程式邏輯進行測試?是否每次都要重新佈署在手機或是模擬器上,然而Android平台的特性,每次部屬都會耗費大量時間而降低開發效能,根據上面的介紹,MVP的P把所有與用戶對應的邏輯都集中在這裡,所以可以透過MOCK一個View及Model來測試P,進而省去部屬跟測試的時間。

三. MVC跟MVP差異

首先我們看到了以下的圖


MVC:

  • View可以與Model直接互動 

  • Controller是用於更新UI介面和數據實例,並且可以被多個View共享
View層接受用戶的輸入,然後通過Controller修改對應的Model實例;同時,當Model實例的數據發生變化的時候,需要修改UI界面,可以通過Controller更新界面。 View層也可以直接更新Model實例的數據,而不用每次都通過Controller,這樣對於一些簡單的數據更新工作會變得方便許多。


MVP:
  • View不直接與Model交互,而是通過與Presenter交互來與Model間接交互 

  • Presenter與View的交互是通過接口來進行的,更有利於添加單元測試 

  • 通常View與Presenter是一對一的,但複雜的View可能綁定多個Presenter來處理邏輯
這樣的話,Activity的工作變簡單了,只用來響應生命週期,其他工作都丟到Presenter中去完成。從上圖可以看出,Presenter是Model和View之間的橋樑,為了讓結構變得更加簡單,View並不能直接對Model進行操作,這也是MVP與MVC最大的不同之處。

四. demo實作

那我們來實作一個簡單的loginApp,成功的話就對丟出Toast訊息



這個布局很簡單,我就不多介紹



再讓我們來看到架構,分成Model,Presenter,View跟主要的activity, 個人習慣及建議是從Presenter開始著手,因為當你設計一個程式,你會知道你需要什麼功能以及你的layout,進而知道你的邏輯function到底要設計什麼,而Presenter就是在處理這些!

Presenter

讓我們來看看Presenter,其實我們要做的邏輯很簡單,登入無非是抓去使用者資料去比對資料庫!所以很清楚的知道我們的主要邏輯就是抓View的String 物件,再去Model裡抓值比對。
所以一開始我在onloginclick(String,String)設了String 參數,然後再去Model裡的checkaccount()裡抓值比對,然後我希望當我輸入完我的欄位值可以清空,所以再View寫了clearEdittext(),最後我想推出是否成功登入或失敗的訊息,所以我在View寫了toastmsg(參數),參數是在Model裡做了如果是成功登入我會回傳訊息(也就是checksuccessaccountmsg()),以上就是Presenter要做的事其實很簡單。

其實Model跟View 誰先寫誰後寫我覺得差不多,我個人習慣是在Presenter其實就順便把Interface 的View 一起寫好,畢竟是在最後Activity才要實做出來。

View (interface)

那View的程式介紹其實在上面就有提到,只需要在Activity實作因為他是個介面(interface)

Model

Model就是存取資料的地方,那我在這裡其實做了偷吃步,我並沒有去資料庫的建置,我只假設我的帳號是andy就可以登入,除了這個都是失敗,這裡的部分就請大家自己實作瞜!其實做好的方式是把帳號密碼取出來並回傳到Presenter去做邏輯測判斷,那我在一開始其實設置了Globla變數 msg ,並在checksuccessaccountmsg()做回傳。

Activity



Activity實作View interface,而我在一開始宣告new一個Presenter物件,並傳遞view跟model,那大家有沒有看到我是用Presenter.onCreate(),這裡是因為MVC都是透過Presenter去做,包括介面更新所以我想讓View做更少,當然其實簡單點還是用原本的setContentView來也可以,實作完方法最後就只剩點擊事件,那我在XML設置Button onClick=btn_login,也就是叫Presenter開始做事情啦!

可以看到,View只負責處理與用戶進行交互,並把數據相關的邏輯操作都扔給了Presenter去做。而Presenter調用Model處理完數據之後,再通過InterfaceView更新ActivyView顯示的訊息。

附上github : https://github.com/andy086912597/Mvplogindemo,大家有興趣可以下載了解,有問題也可以互相討論說明,謝謝!

參考網址