Android開發(fā)注意事項(xiàng)(4-6)

注意事項(xiàng):

  1. 權(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ù)中。

  1. 調(diào)試與測試
    調(diào)試
  1. 記錄棧跟蹤的診斷性日志
    ??例:檢查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())。
  1. 利用調(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í)行跳出,跳出此方法。

測試

單元測試

整合測試

  1. 設(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ù)控制流與用戶交互.jpg

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)證。

操作步驟:

  1. 啟用數(shù)據(jù)綁定:
app/build.gradle:

 buildTypes {
        release {
         .......
        }
    }
    
    dataBinding{
        enabled = true
    }

dataBinding{enabled = true},會打開IDE的整合功能,允許使用數(shù)據(jù)綁定產(chǎn)生的類,并把他們整合到編譯里去。

  1. 將一般布局改造為數(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>
  1. 實(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ù)綁定獲取視圖。

  1. 創(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();
    }
  1. 綁定數(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)的值即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容