前言:
这周主要还是以学习、看代码、看书为主,就着《Android开发艺术探索》和《Effective Java》下饭,学习了一波关于注解的知识 ,以及主要使用注解加快开发速度的ButterKnife,另外写了几个简单的Demo。也算是回顾了之前所落下的安卓知识了,算是快速充电的一周,另外LeetCode也在刷刷刷。
以下是本周的主要学习记录点:
- ButterKnife的使用
- Android Context 的理解
- Android手机验证码登录的实现
- Android关于使用相机的点点滴滴
- Android第三方登录的实现(未实现!要钱!)
- SharedPreference的应用(登录维持)
- 刷Leetcode
- RadioGroup+Fragment实现带底部导航栏的界面切换(回顾:之前项目使用的是BottomBar实现)
- RxJava从放弃到开始
坑有点多我们慢慢的一个个填
- ButterKnife的使用
ButterKnife是一个Android系统的View注入框架,以前总是要写很多findViewById来找到View对象,有了ButterKnife可以很轻松的省去这些步骤。。最重要的一点,使用ButterKnife对性能基本没有损失,因为ButterKnife用到的注解并不是在运行时反射的,而是在编译的时候生成新的class。项目集成起来也是特别方便,使用起来也是特别简单。
ButterKnife项目地址:https://github.com/JakeWharton/butterknife
▲ ButterKnife的优势:
1、非常强大的View绑定和OnClick绑定,让你的代码瞬间整洁好多好多,再也看不见那么多的
btn_function = (Button)findViewById(R.id.btn_func)
了!简化代码也算是解放程序员了嘛,还提高了开发效率,何乐而不为呢。
改造后:
// View绑定
@BindView(R.id.pi_image)
public CircleImageView mHeader_iv;
@BindView(R.id.btn_personal_info_modify_image)
public Button btn_personal_info_modify_image;
@BindView(R.id.pi_name)
public LinearLayout piLl_name;
@BindView(R.id.pi_league_num)
public LinearLayout piLl_league_num;
@BindView(R.id.pi_gender)
public LinearLayout piLl_gender;
...
//OnClick绑定
@OnClick(R.id.pi_name)
public void pill_name_OnClick(){
Toast.makeText(this,"点击",Toast.LENGTH_SHORT).show();
startActivity(new Intent(personalInfo_Activity.this,pi_modifyName_Activity.class));
Log.i(LOGTAG, "点击");
}
2、方便的处理Adapter里的ViewHolder绑定问题
3、运行时不会影响APP效率,使用配置方便
4、代码清晰,可读性强
▲ 配置方式
在build.gradle中配置如下:
dependencies {
...
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}
其实最新的butterknife版本已经更新到
implementation 'com.jakewharton:butterknife:10.0.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
但是呢,当我使用10.0.0的时候,发现了AndroidSupport与Androidx的冲突,报了如下错误:
anifest merger failed : Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory) from [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91
is also present at [androidx.core:core:1.0.0] AndroidManifest.xml:22:18-86 value=(androidx.core.app.CoreComponentFactory).
Suggestion: add 'tools:replace="android:appComponentFactory"' to element at AndroidManifest.xml:36:5-364:19 to override.
按照Androidx和Android support库共存问题解决文章中的两种解决方式,第一是将build tools更新到3.2.0,gradle更新到4.6,依赖库统一更新到28.0.0,再把项目迁移至Androidx,具体操作看前提链接。第二是回退butterKnife版本,我将版本回退至8.8.1后使用正常。
▲使用体会:
1、在Activity 类中绑定 :ButterKnife.bind(this);必须在setContentView();之后绑定;且父类bind绑定后,子类不需要再bind。
2、在非Activity 类(eg:Fragment、ViewHold)中绑定: ButterKnife.bind(this,view);这里的this不能替换成getActivity()。
3、在Activity中不需要做解绑操作,在Fragment 中必须在onDestroyView()中做解绑操作。
4、使用ButterKnife修饰的方法和控件,不能用private or static 修饰,否则会报错。错误: @BindView fields must not be private or static.
▲主要使用:
@BindView—->绑定一个view;id为一个view 变量
@BindViews —-> 绑定多个view;id为一个view的list变量
@BindArray—-> 绑定string里面array数组;@BindArray(R.array.city ) String[] citys ;
@BindBitmap—->绑定图片资源为Bitmap;@BindBitmap( R.mipmap.wifi ) Bitmap bitmap;
@BindBool —->绑定boolean值
@BindColor —->绑定color;@BindColor(R.color.colorAccent) int black;
@BindDimen —->绑定Dimen;@BindDimen(R.dimen.borth_width) int mBorderWidth;
@BindDrawable —-> 绑定Drawable;@BindDrawable(R.drawable.test_pic) Drawable mTestPic;
@BindFloat —->绑定float
@BindInt —->绑定int
@BindString —->绑定一个String id为一个String变量;@BindString( R.string.app_name ) String meg;
@OnClick—->点击事件
@OnCheckedChanged —->选中,取消选中
@OnEditorAction —->软键盘的功能键
@OnFocusChange —->焦点改变
@OnItemClick item—->被点击(注意这里有坑,如果item里面有Button等这些有点击的控件事件的,需要设置这些控件属性focusable为false)
@OnItemLongClick item—->长按(返回真可以拦截onItemClick)
@OnItemSelected —->item被选择事件
@OnLongClick —->长按事件
@OnPageChange —->页面改变事件
@OnTextChanged —->EditText里面的文本变化事件
@OnTouch —->触摸事件
@Optional —->选择性注入,如果当前对象不存在,就会抛出一个异常,为了压制这个异常,可以在变量或者方法上加入一下注解,让注入变成选择性的,如果目标View存在,则注入, 不存在,则什么事情都不做
- Android Context的理解
对于Context的理解主要是浏览了Context都没弄明白,还怎么做Android开发?这篇文章,对Android的Context有了一些理解。
我们一般将Context理解为“上下文”或者“场景”,如果你仍然觉得很抽象,不好理解。在这里我给出一个可能不是很恰当的比喻,希望有助于大家的理解:一个Android应用程序,可以理解为一部电影或者一部电视剧,Activity,Service,Broadcast Receiver,Content Provider这四大组件就好比是这部戏里的四个主角:胡歌,霍建华,诗诗,Baby。他们是由剧组(系统)一开始就定好了的,整部戏就是由这四位主演领衔担纲的,所以这四位主角并不是大街上随随便便拉个人(new 一个对象)都能演的。有了演员当然也得有摄像机拍摄啊,他们必须通过镜头(Context)才能将戏传递给观众,这也就正对应说四大组件(四位主角)必须工作在Context环境下(摄像机镜头)。那Button,TextView,LinearLayout这些控件呢,就好比是这部戏里的配角或者说群众演员,他们显然没有这么重用,随便一个路人甲路人乙都能演(可以new一个对象),但是他们也必须要面对镜头(工作在Context环境下),所以Button mButton=new Button(Context)是可以的。
在应用程序中Context的具体实现子类就是:Activity,Service,Application。那么Context数量=Activity数量+Service数量+1。
正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出-使用Context的正确姿势:
1.当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2.不要让生命周期长于Activity的对象持有到Activity的引用。
3.尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
- Android手机验证码登录的实现
login_Activity.java
public class login_Activity extends AppCompatActivity {
@BindView(R.id.tv_contract)
public TextView tv_contract;
@BindView(R.id.ev_phone_Number)
public EditText ev_phone_Number;
@BindView(R.id.ev_validator)
public EditText ev_validator;
@BindView(R.id.btn_login_confirm)
public Button btn_login_confirm;
@BindView(R.id.btn_get_validater)
public Button btn_get_validater;
private TimeCount time_count;
private EventHandler eh; //事件接收器
private SpUtil sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
ButterKnife.bind(this);
time_count = new TimeCount(60000, 1000); //设置验证码定时器
sp = new SpUtil(this);
tv_contractClick(); //点击服务协议和隐私政策的响应
Handler_Validator_Event(); //处理点击确定按钮后的验证码回调工作
}
/**
* 对同意协议和隐私政策进行特殊处理,使其可点击并处理相应逻辑
*/
private void tv_contractClick() {
final SpannableStringBuilder style = new SpannableStringBuilder();
style.append("在点击“继续”时,我已同意服务协议和隐私政策");
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(login_Activity.this, "触发点击事件:服务协议!", Toast.LENGTH_SHORT).show();
}
};
ClickableSpan clickableSpan2 = new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(login_Activity.this, "触发点击事件:隐私政策!", Toast.LENGTH_SHORT).show();
}
};
style.setSpan(clickableSpan, 13, 17, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
style.setSpan(clickableSpan2,18,22,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv_contract.setText(style);
//设置部分文字颜色
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.parseColor("#40a9ff"));
ForegroundColorSpan foregroundColorSpan2 = new ForegroundColorSpan(Color.parseColor("#40a9ff"));
//设置需要特殊处理的文字
style.setSpan(foregroundColorSpan, 13, 17, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
style.setSpan(foregroundColorSpan2, 18, 22, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//配置给TextView
tv_contract.setMovementMethod(LinkMovementMethod.getInstance());
tv_contract.setText(style);
}
private void bindview() {
time_count = new TimeCount(60000, 1000);
}
@OnClick(R.id.btn_login_confirm)
public void btn_login_confirm_OnClick(){
if (!ev_phone_Number.getText().toString().trim().equals("")) {
if (Textutil.checkTel(ev_phone_Number.getText().toString().trim())) {
if (!ev_validator.getText().toString().trim().equals("")) {
SMSSDK.submitVerificationCode("+86",ev_phone_Number.getText().toString().trim(),ev_validator.getText().toString().trim());//提交验证
Log.e("SMSSDK",ev_validator.getText().toString().trim());
}else{
Toast.makeText(login_Activity.this, "请输入验证码", Toast.LENGTH_SHORT).show();
}
}else{
Toast.makeText(login_Activity.this, "请输入正确的手机号码", Toast.LENGTH_SHORT).show();
}
}else{
Toast.makeText(login_Activity.this, "请输入手机号码", Toast.LENGTH_SHORT).show();
}
}
@OnClick(R.id.btn_get_validater)
public void btn_get_validater_OnClick(){
//SMSSDK.getSupportedCountries();//获取短信目前支持的国家列表
if(!ev_phone_Number.getText().toString().trim().equals("")){
if (Textutil.checkTel(ev_phone_Number.getText().toString().trim())) {
SMSSDK.getVerificationCode("+86",ev_phone_Number.getText().toString());//获取验证码
time_count.start();
}else{
Toast.makeText(login_Activity.this, "请输入正确的手机号码", Toast.LENGTH_SHORT).show();
}
}else{
Toast.makeText(login_Activity.this, "请输入手机号码", Toast.LENGTH_SHORT).show();
}
}
/**
* 倒数计数器内部类
*/
class TimeCount extends CountDownTimer {
public TimeCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
/**
* 每一次ontick都会更新UI
* @param l
*/
@Override
public void onTick(long l) {
btn_get_validater.setClickable(false);
btn_get_validater.setText(l/1000 + "秒后重新获取");
}
/**
* 当倒数计时器完成时回到获取验证码
*/
@Override
public void onFinish() {
btn_get_validater.setClickable(true);
btn_get_validater.setText("获取验证码");
}
}
/**
* 初始化事件接收器
*/
private void Handler_Validator_Event(){
eh = new EventHandler(){
@Override
public void afterEvent(int event, int result, Object data) {
if (result == SMSSDK.RESULT_COMPLETE) { //回调完成
if (event == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) { //提交验证码成功
Log.e("SMSSDK","verifyOK");
/**
* 这里应该是一个okhttp3请求,和服务器交互,并将用户名和头像信息写入sharePreference进行持久化
*/
// String url = "http://这里写的是接口地址(具体接收格式要看后台怎么给)";
// Map params = new HashMap();
// params.put("number",ev_phone_Number.getText().toString().trim() );
// OkHttpUtil.postAsync(url, params, new OkHttpUtil.DataCallBack() {
// @Override
// public void requestFailure(Request request, IOException e) {
// Log.i("请求失败", "请求失败" + request.toString() + e.toString());
// }
// @Override
// public void requestSuccess(String result) throws Exception {
// Toast.makeText(login_Activity.this,"请求成功",Toast.LENGTH_SHORT).show();
// Log.i("请求成功", result);
// }
// });
} else if (event == SMSSDK.EVENT_GET_VERIFICATION_CODE){ //获取验证码成功
} else if (event ==SMSSDK.EVENT_GET_SUPPORTED_COUNTRIES){ //返回支持发送验证码的国家列表
}
} else{
sp.saveString("username",ev_phone_Number.getText().toString().trim());
sp.saveString("username","机器人");
Log.e("SMSSDK","verifyERROR");
Looper.prepare();
Toast.makeText(login_Activity.this, "验证码输入错误,请重试", Toast.LENGTH_SHORT).show();
startActivity(new Intent(login_Activity.this, main_Activity.class)); //页面跳转
Looper.loop();
((Throwable)data).printStackTrace();
}
}
};
SMSSDK.registerEventHandler(eh); //注册短信回调
}
@Override
protected void onDestroy() {
super.onDestroy();
SMSSDK.unregisterEventHandler(eh); //取消短信回调,销毁
}
}
login_activity.xml
工具类-正则匹配手机号
public class Textutil {
/**
* 正则匹配手机号码
* @param tel
* @return
*/
public static boolean checkTel(String tel){
Pattern p = Pattern.compile("^[1][3,4,5,7,8][0-9]{9}$");
Matcher matcher = p.matcher(tel);
return matcher.matches();
}
}
要注意哦,未成为开发者之前每台手机每天只能接受10封短信。主要是在AfterEvent方法的重写去实现验证码验证后回调的逻辑。