注意事项:
- 权限申请
普通权限申请:
对于普通权限,即那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,在AndroidManifest中文件写入即可。例如:
运行时权限申请:
对于运行时权限申请,必须要由用户手动点击授权才可以。相关权限都是危险权限,即那些可能会触及用户隐私或者对设备安全性造成影响的权限。如获取设备联系人信息、地理位置等。下面以CALL_PHONE这个权限为例
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;
}
}
}
第一步先要判断用户是不是已经授权了,借助的是ContextCompat.checkSelfPermission()方法。checkSelfPermission()方法接受两个参数,第一个参数是Context,第二个参数是具体的权限名。然后使用方法的返回值和PackageManager.PERMISSION_GRANTED做比较,相等就说明用户已经授权,不等就表示用户没有授权。
如果没有授权的话,则需要调用ActivityCompat.requestPermissions()方法来向用户申请授权。requestPermission()方法接收3个参数,第一个参数要求是Activity的实例,第二个参数是一个string数组,用来放申请的权限名,第三个参数是请求码。
之后,系统会弹出一个权限申请的对话框,然后用户同意或拒接权限申请。最后会回调onRequestPermissionResult()方法,授权的结果封装在grantResults参数中。
- 调试与测试
调试
- 记录栈跟踪的诊断性日志
例:检查doTest()方法是否被调用,可以使用Log.d(String, String, Throwable)。此方法记录并输出整个栈跟踪日志。可以很容易看出doTest()方法在哪些地方被调用了。
作为参数传入Log.d(String, String, Throwable)方法的异常,不一定就是已捕获的抛出异常。可以创建一个全新的Exception,把它作为不抛出的异常对象传入。例:Log.d("TAG", "TAG", new Exception())。
- 利用调试器设置断点调试
使用AndroidStudio自带的调试器调试doTest()方法,首先要在刚方法内设置断点,以确认该方法是否被调用。断点会在断点设置行的前一行处停止代码执行。
设置好断点以后,单击Debug按钮,启动调试。
在调试视图中,左半部分为栈列表,可查看方法的调用顺序;右半部分为变量视图,可以让观察程序中各个变量的值。
通过单击单步执行按钮,继续执行代码,查看代码更深层次的调用(源码内的方法调用),观察变量的值,直至找出问题的关键。当想跳过查看某一行代码的执行细节,可用单步执行跳过按钮,跳过此行代码;当想跳出某个方法的执行细节,可点击单步执行跳出,跳出此方法。
测试
单元测试
整合测试
- 设计模式
MVC设计模式
M:模型、V:视图(布局)、C:控制器
一些Android应用基于M-V-C架构模式(即模型-视图-控制器的架构模式)进行设计。MVC模式表明,应用的任何对象,归根结底都属于模型对象、视图对象以及控制器对象中的一种。
模型对象:存储着应用的数据,模型类通常用来映射与应用相关的一些事物。模型对象不关心用户界面,它为存储和管理应用数据而生。
视图对象:在屏幕上绘制,以及响应用户的输入。凡是在屏幕上能看见的对象,就是视图对象。
控制器对象:含有应用的逻辑单元,是视图对象与模型对象的联系纽带。控制器对象响应视图对象触发的各类事件,此外还管理着模型对象与视图层间的数据流动。
在响应诸如单击按钮等用户事件时,对象间的交互控制着数据流。模型对象与视图对象不直接交互。控制器作为他们之间的联系纽带,接收对象发送的消息,然后向其他对象发送操作指令。
MVC设计模式的好处:
随着应用功能的持续扩展,应用往往会变得过于复杂。把java类以模型层、视图层、控制器层进行分类组织,我们就可以按层而非一个个类来考虑设计开发了。例如想要升级视图层,在界面上添加一个按钮,就不需要考虑模型类。
MVC的设计模式还便于复用类。相比功能多而全的类,功能单一的专用类更有利于代码复用。
MVC设计模式的缺点:
1. MVC的真实存在是MC(V),Model和Controller根本没办法分开,并且数据和View严重耦合,也导致View也包含了业务逻辑。
2. Controller会变得很厚很复杂。
MVP设计模式
M:模型、V:视图(布局)、P:表现者。MVC的演变模式,将控制器(Controller)变为Presenter(表现者)。目的在于将模型层(Model)与视图层(View)解耦。,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。
如上图所示,MVP中将MVC中原有的V,拆分为V+P,并将View层实现接口化。这样View层彻底与数据和业务逻辑分离,View层只关心和用户的交互。即用户进行某种操作,View层只负责提供响应接口,具体的业务实现和数据操作都交由Presenter(Presenter必须要拿到View和Model的实例)。
MVP模式实例:
Model层:
/**
定义业务接口
*/
public interface IUserBiz {
public void login(String username, String password, OnLoginListener loginListener);
}
/**
结果回调接口
*/
public interface OnLoginListener {
void loginSuccess(User user);
void loginFailed();
}
/**
具体Model的实现
*/
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实现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的实现类
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线程执行
mHandler.post(new Runnable()
{
@Override
public void run()
{
userLoginView.toMainActivity(user);
userLoginView.hideLoading();
}
});
}
@Override
public void loginFailed()
{
//需要在UI线程执行
mHandler.post(new Runnable()
{
@Override
public void run()
{
userLoginView.showFailedError();
userLoginView.hideLoading();
}
});
}
});
}
public void clear()
{
userLoginView.clearUserName();
userLoginView.clearPassword();
}
}
MVVM设计模式:
M:模型、V:视图(布局)、VM:视图模型。MVP模式的改进版,MVVM架构很好地把控制器(表现者)里的臃肿代码抽到布局文件里,让开发人员很容易看出哪些时动态界面。同时,它也抽出部分动态控制器代码放入ViewModel类,大大方便了开发测试和验证。
操作步骤:
- 启用数据绑定:
app/build.gradle:
buildTypes {
release {
.......
}
}
dataBinding{
enabled = true
}
dataBinding{enabled = true},会打开IDE的整合功能,允许使用数据绑定产生的类,并把他们整合到编译里去。
- 将一般布局改造为数据绑定布局:
要在布局里使用数据绑定,首先要把一般布局改造为数据绑定布局。具体做法就是把整个布局定义放入标签 。之后,数据绑定工具会帮助生成一个绑定类。新产生的绑定类默认以布局文件命名。
fragment_test.xml
- 实例化绑定类
现在,activity_main.xml已经有了一个叫FragmentTestBinding的绑定类。实例化视图层级结构时,不要再使用LayoutInflater,而是使用DataBindingUtil实例化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();
}
以上就是简单的数据绑定,不用findViewById()方法,转而使用数据绑定获取视图。
- 创建、整合视图模型(ViewModel)
首先,创建视图模型ViewModel:
public class TestViewModel {
//Model层
private TestModel mTestModel;
public TestViewModel(TestModel testModel) {
mTestModel = testModel;
}
public TestModel getTestModel() {
return mTestModel;
}
........
}
然后,把视图模型整合到布局文件里:
最后,关联使用视图模型:
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
FragmentTestBinding binding = DataBindingUtil.inflate(inflater, R.layout.activity_main,
container, false);
//关联使用视图模型
binding.setViewModel(new TestViewModel(new TestModel()));
//获取视图模型,并进行更新等操作
binding.getViewModel().setSomeThing();
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
return binding.getRoot();
}
- 绑定数据观察
当我们更新Model层的数据时,View层并不知情,ViewModel层需要实现数据绑定的BaseObservable接口。这个接口可以让绑定类在ViewModel上设置监听器,只要视图模型有变化,绑定类立即会接到回调。
public class TestViewModel extends BaseObservable {
//Model层
private TestModel mTestModel;
public TestViewModel(TestModel testModel) {
mTestModel = testModel;
}
public void setSomeThing() {
.......
//通知绑定类,视图模型上所有可绑定的属性已经更新
notifyChange();
//通知绑定类,视图模型上只有用@Bindable注解的方法值有变
notifyPropertyChanged(com.futuring.renrenslidinglayout.BR.testModel);
}
//注解视图模型中可绑定的属性,当属性更新时,重新调用此方法
@Bindable
public TestModel getTestModel() {
return mTestModel;
}
public void onClick() {
}
public void onButtonClick(){
}
}
当ViewModel中的Model信息更新,调用notifyChange()通知绑定类,在View层中更新所有可绑定的属性。调用notifyPropertyChanged(int BR.),通知绑定类,在View层中只更新与@Bindable注解的方法有关的值即可。