注意事項(xiàng):
- 權(quán)限申請
普通權(quán)限申請:
??對于普通權(quán)限,即那些不會直接威脅到用戶的安全和隱私的權(quán)限,對于這部分權(quán)限申請,在AndroidManifest中文件寫入即可。例如:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.futuring.slidemenudemo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
</manifest>
運(yùn)行時權(quán)限申請:
??對于運(yùn)行時權(quán)限申請,必須要由用戶手動點(diǎn)擊授權(quán)才可以。相關(guān)權(quán)限都是危險權(quán)限,即那些可能會觸及用戶隱私或者對設(shè)備安全性造成影響的權(quán)限。如獲取設(shè)備聯(lián)系人信息、地理位置等。下面以CALL_PHONE這個權(quán)限為例
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.CALL_PHONE}, 1);
else{
XXXXXXXX
}
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == packageManager.PERMISSION_GRANTED) {
XXXXXXXXX;
}
}
}
??第一步先要判斷用戶是不是已經(jīng)授權(quán)了,借助的是ContextCompat.checkSelfPermission()方法。checkSelfPermission()方法接受兩個參數(shù),第一個參數(shù)是Context,第二個參數(shù)是具體的權(quán)限名。然后使用方法的返回值和PackageManager.PERMISSION_GRANTED做比較,相等就說明用戶已經(jīng)授權(quán),不等就表示用戶沒有授權(quán)。
??如果沒有授權(quán)的話,則需要調(diào)用ActivityCompat.requestPermissions()方法來向用戶申請授權(quán)。requestPermission()方法接收3個參數(shù),第一個參數(shù)要求是Activity的實(shí)例,第二個參數(shù)是一個string數(shù)組,用來放申請的權(quán)限名,第三個參數(shù)是請求碼。
??之后,系統(tǒng)會彈出一個權(quán)限申請的對話框,然后用戶同意或拒接權(quán)限申請。最后會回調(diào)onRequestPermissionResult()方法,授權(quán)的結(jié)果封裝在grantResults參數(shù)中。
- 調(diào)試與測試
調(diào)試
- 記錄棧跟蹤的診斷性日志
??例:檢查doTest()方法是否被調(diào)用,可以使用Log.d(String, String, Throwable)。此方法記錄并輸出整個棧跟蹤日志??梢院苋菀卓闯鰀oTest()方法在哪些地方被調(diào)用了。
??作為參數(shù)傳入Log.d(String, String, Throwable)方法的異常,不一定就是已捕獲的拋出異常。可以創(chuàng)建一個全新的Exception,把它作為不拋出的異常對象傳入。例:Log.d("TAG", "TAG", new Exception())。
- 利用調(diào)試器設(shè)置斷點(diǎn)調(diào)試
??使用AndroidStudio自帶的調(diào)試器調(diào)試doTest()方法,首先要在剛方法內(nèi)設(shè)置斷點(diǎn),以確認(rèn)該方法是否被調(diào)用。斷點(diǎn)會在斷點(diǎn)設(shè)置行的前一行處停止代碼執(zhí)行。
??設(shè)置好斷點(diǎn)以后,單擊Debug按鈕,啟動調(diào)試。斷點(diǎn)調(diào)試.JPG調(diào)試視圖.JPG
??在調(diào)試視圖中,左半部分為棧列表,可查看方法的調(diào)用順序;右半部分為變量視圖,可以讓觀察程序中各個變量的值。
??通過單擊單步執(zhí)行按鈕,繼續(xù)執(zhí)行代碼,查看代碼更深層次的調(diào)用(源碼內(nèi)的方法調(diào)用),觀察變量的值,直至找出問題的關(guān)鍵。當(dāng)想跳過查看某一行代碼的執(zhí)行細(xì)節(jié),可用單步執(zhí)行跳過按鈕,跳過此行代碼;當(dāng)想跳出某個方法的執(zhí)行細(xì)節(jié),可點(diǎn)擊單步執(zhí)行跳出,跳出此方法。
測試
單元測試
整合測試
- 設(shè)計(jì)模式
MVC設(shè)計(jì)模式
??M:模型、V:視圖(布局)、C:控制器
??一些Android應(yīng)用基于M-V-C架構(gòu)模式(即模型-視圖-控制器的架構(gòu)模式)進(jìn)行設(shè)計(jì)。MVC模式表明,應(yīng)用的任何對象,歸根結(jié)底都屬于模型對象、視圖對象以及控制器對象中的一種。
??模型對象:存儲著應(yīng)用的數(shù)據(jù),模型類通常用來映射與應(yīng)用相關(guān)的一些事物。模型對象不關(guān)心用戶界面,它為存儲和管理應(yīng)用數(shù)據(jù)而生。
??視圖對象:在屏幕上繪制,以及響應(yīng)用戶的輸入。凡是在屏幕上能看見的對象,就是視圖對象。
??控制器對象:含有應(yīng)用的邏輯單元,是視圖對象與模型對象的聯(lián)系紐帶??刂破鲗ο?strong>響應(yīng)視圖對象觸發(fā)的各類事件,此外還管理著模型對象與視圖層間的數(shù)據(jù)流動。mvc架構(gòu)模式.jpg
??在響應(yīng)諸如單擊按鈕等用戶事件時,對象間的交互控制著數(shù)據(jù)流。模型對象與視圖對象不直接交互??刂破髯鳛樗麄冎g的聯(lián)系紐帶,接收對象發(fā)送的消息,然后向其他對象發(fā)送操作指令。

MVC設(shè)計(jì)模式的好處:
??隨著應(yīng)用功能的持續(xù)擴(kuò)展,應(yīng)用往往會變得過于復(fù)雜。把java類以模型層、視圖層、控制器層進(jìn)行分類組織,我們就可以按層而非一個個類來考慮設(shè)計(jì)開發(fā)了。例如想要升級視圖層,在界面上添加一個按鈕,就不需要考慮模型類。
??MVC的設(shè)計(jì)模式還便于復(fù)用類。相比功能多而全的類,功能單一的專用類更有利于代碼復(fù)用。
MVC設(shè)計(jì)模式的缺點(diǎn):
??1. MVC的真實(shí)存在是MC(V),Model和Controller根本沒辦法分開,并且數(shù)據(jù)和View嚴(yán)重耦合,也導(dǎo)致View也包含了業(yè)務(wù)邏輯。
??2. Controller會變得很厚很復(fù)雜。
MVP設(shè)計(jì)模式
??M:模型、V:視圖(布局)、P:表現(xiàn)者。MVC的演變模式,將控制器(Controller)變?yōu)镻resenter(表現(xiàn)者)。目的在于將模型層(Model)與視圖層(View)解耦。,MVP與MVC有著一個重大的區(qū)別:在MVP中View并不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進(jìn)行的,所有的交互都發(fā)生在Presenter內(nèi)部,而在MVC中View會直接從Model中讀取數(shù)據(jù)而不是通過 Controller。
mvp架構(gòu)模式.jpg
??如上圖所示,MVP中將MVC中原有的V,拆分為V+P,并將View層實(shí)現(xiàn)接口化。這樣View層徹底與數(shù)據(jù)和業(yè)務(wù)邏輯分離,View層只關(guān)心和用戶的交互。即用戶進(jìn)行某種操作,View層只負(fù)責(zé)提供響應(yīng)接口,具體的業(yè)務(wù)實(shí)現(xiàn)和數(shù)據(jù)操作都交由Presenter(Presenter必須要拿到View和Model的實(shí)例)。
MVP模式實(shí)例:
Model層:
/**
定義業(yè)務(wù)接口
*/
public interface IUserBiz {
public void login(String username, String password, OnLoginListener loginListener);
}
/**
結(jié)果回調(diào)接口
*/
public interface OnLoginListener {
void loginSuccess(User user);
void loginFailed();
}
/**
具體Model的實(shí)現(xiàn)
*/
public class UserBiz implements IUserBiz {
@Override
public void login(final String username, final String password, final OnLoginListener loginListener)
{
//模擬子線程耗時操作
new Thread()
{
@Override
public void run()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
//模擬登錄成功
if ("zhy".equals(username) && "123".equals(password))
{
User user = new User();
user.setUsername(username);
user.setPassword(password);
loginListener.loginSuccess(user);
} else
{
loginListener.loginFailed();
}
}
}.start();
}
}
View層:
public interface IUserLoginView {
String getUserName();
String getPassword();
void clearUserName();
void clearPassword();
void showLoading();
void hideLoading();
void toMainActivity(User user);
void showFailedError();
}
Activity實(shí)現(xiàn)View接口:
public class UserLoginActivity extends ActionBarActivity implements IUserLoginView {
private EditText mEtUsername, mEtPassword;
private Button mBtnLogin, mBtnClear;
private ProgressBar mPbLoading;
private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_login);
initViews();
}
private void initViews()
{
mEtUsername = (EditText) findViewById(R.id.id_et_username);
mEtPassword = (EditText) findViewById(R.id.id_et_password);
mBtnClear = (Button) findViewById(R.id.id_btn_clear);
mBtnLogin = (Button) findViewById(R.id.id_btn_login);
mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);
mBtnLogin.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
mUserLoginPresenter.login();
}
});
mBtnClear.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
mUserLoginPresenter.clear();
}
});
}
@Override
public String getUserName()
{
return mEtUsername.getText().toString();
}
@Override
public String getPassword()
{
return mEtPassword.getText().toString();
}
@Override
public void clearUserName()
{
mEtUsername.setText("");
}
@Override
public void clearPassword()
{
mEtPassword.setText("");
}
@Override
public void showLoading()
{
mPbLoading.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading()
{
mPbLoading.setVisibility(View.GONE);
}
@Override
public void toMainActivity(User user)
{
Toast.makeText(this, user.getUsername() +
" login success , to MainActivity", Toast.LENGTH_SHORT).show();
}
@Override
public void showFailedError()
{
Toast.makeText(this,
"login failed", Toast.LENGTH_SHORT).show();
}
}
Presenter層:
public class UserLoginPresenter {
private IUserBiz userBiz;
private IUserLoginView userLoginView;
private Handler mHandler = new Handler();
//Presenter必須要能拿到View和Model的實(shí)現(xiàn)類
public UserLoginPresenter(IUserLoginView userLoginView)
{
this.userLoginView = userLoginView;
this.userBiz = new UserBiz();
}
public void login()
{
userLoginView.showLoading();
userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener()
{
@Override
public void loginSuccess(final User user)
{
//需要在UI線程執(zhí)行
mHandler.post(new Runnable()
{
@Override
public void run()
{
userLoginView.toMainActivity(user);
userLoginView.hideLoading();
}
});
}
@Override
public void loginFailed()
{
//需要在UI線程執(zhí)行
mHandler.post(new Runnable()
{
@Override
public void run()
{
userLoginView.showFailedError();
userLoginView.hideLoading();
}
});
}
});
}
public void clear()
{
userLoginView.clearUserName();
userLoginView.clearPassword();
}
}
MVVM設(shè)計(jì)模式:
??M:模型、V:視圖(布局)、VM:視圖模型。MVP模式的改進(jìn)版,MVVM架構(gòu)很好地把控制器(表現(xiàn)者)里的臃腫代碼抽到布局文件里,讓開發(fā)人員很容易看出哪些時動態(tài)界面。同時,它也抽出部分動態(tài)控制器代碼放入ViewModel類,大大方便了開發(fā)測試和驗(yàn)證。
操作步驟:
- 啟用數(shù)據(jù)綁定:
app/build.gradle:
buildTypes {
release {
.......
}
}
dataBinding{
enabled = true
}
dataBinding{enabled = true},會打開IDE的整合功能,允許使用數(shù)據(jù)綁定產(chǎn)生的類,并把他們整合到編譯里去。
- 將一般布局改造為數(shù)據(jù)綁定布局:
??要在布局里使用數(shù)據(jù)綁定,首先要把一般布局改造為數(shù)據(jù)綁定布局。具體做法就是把整個布局定義放入<layout>標(biāo)簽。之后,數(shù)據(jù)綁定工具會幫助生成一個綁定類。新產(chǎn)生的綁定類默認(rèn)以布局文件命名。
fragment_test.xml
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</layout>
- 實(shí)例化綁定類
??現(xiàn)在,activity_main.xml已經(jīng)有了一個叫FragmentTestBinding的綁定類。實(shí)例化視圖層級結(jié)構(gòu)時,不要再使用LayoutInflater,而是使用DataBindingUtil實(shí)例化FragmentTestBinding類。FragmentTest類有兩個引用:getRoot()和recyclerView,前者指整個布局,后者指RecyclerView。
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
FragmentTestBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_test,
container, false);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
return binding.getRoot();
}
??以上就是簡單的數(shù)據(jù)綁定,不用findViewById()方法,轉(zhuǎn)而使用數(shù)據(jù)綁定獲取視圖。
- 創(chuàng)建、整合視圖模型(ViewModel)
??首先,創(chuàng)建視圖模型ViewModel:
public class TestViewModel {
//Model層
private TestModel mTestModel;
public TestViewModel(TestModel testModel) {
mTestModel = testModel;
}
public TestModel getTestModel() {
return mTestModel;
}
........
}
??然后,把視圖模型整合到布局文件里:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.futuring.renrenslidinglayout.TestViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button_view"
android:layout_width="match_parent"
android:layout_height="120dp"
//屬性獲取
android:text="@{viewModel.testModel}"
//方法引用(當(dāng)此方法不是只在發(fā)生動作時調(diào)用,使用這種方式)
android:onLongClick="@{viewModel::onClick}"
//綁定監(jiān)聽器(方法只在發(fā)生類似監(jiān)聽的動作時調(diào)用,使用此方式)
android:onClick="@{() -> viewModel.onButtonClick()}"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</layout>
??最后,關(guān)聯(lián)使用視圖模型:
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
FragmentTestBinding binding = DataBindingUtil.inflate(inflater, R.layout.activity_main,
container, false);
//關(guān)聯(lián)使用視圖模型
binding.setViewModel(new TestViewModel(new TestModel()));
//獲取視圖模型,并進(jìn)行更新等操作
binding.getViewModel().setSomeThing();
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
return binding.getRoot();
}
- 綁定數(shù)據(jù)觀察
??當(dāng)我們更新Model層的數(shù)據(jù)時,View層并不知情,ViewModel層需要實(shí)現(xiàn)數(shù)據(jù)綁定的BaseObservable接口。這個接口可以讓綁定類在ViewModel上設(shè)置監(jiān)聽器,只要視圖模型有變化,綁定類立即會接到回調(diào)。
public class TestViewModel extends BaseObservable {
//Model層
private TestModel mTestModel;
public TestViewModel(TestModel testModel) {
mTestModel = testModel;
}
public void setSomeThing() {
.......
//通知綁定類,視圖模型上所有可綁定的屬性已經(jīng)更新
notifyChange();
//通知綁定類,視圖模型上只有用@Bindable注解的方法值有變
notifyPropertyChanged(com.futuring.renrenslidinglayout.BR.testModel);
}
//注解視圖模型中可綁定的屬性,當(dāng)屬性更新時,重新調(diào)用此方法
@Bindable
public TestModel getTestModel() {
return mTestModel;
}
public void onClick() {
}
public void onButtonClick(){
}
}
??當(dāng)ViewModel中的Model信息更新,調(diào)用notifyChange()通知綁定類,在View層中更新所有可綁定的屬性。調(diào)用notifyPropertyChanged(int BR.),通知綁定類,在View層中只更新與@Bindable注解的方法有關(guān)的值即可。

