[Module] 01 - Login/Register, Splash and AsyncTask

重点是线程异步。

  1. 启动页面 - SplashActivity
  2. 登陆与注册
  3. 忘记密码/修改密码
  4. 主界面
  5. 功能引导(类似于viewpager)  // 4, 5,6 放入下一章节
  6. 侧边栏(SlideingMenu, DrawerLayout)
 1 Material Design 定制theme到AndroidManifest的application theme中
 2 ORM框架(无需再用contentprovider或者sqlitedatebasehelper之类的古董工具了)
 3 规划各种业务Bean文件(配合ORM框架)
 4 Http请求框架(volley,推荐使用okHttp,RxJava+Rxandroid+retrofit等)
 5 JSON解析和构建框架(Gson,fastJson,不要用jackson因为比较大,除非需要用嵌套的需求)
 6 设计一个好的请求基类(BaseRequest、BaseResponse)
 7 JWT
 8 BaseActivity和BaseFragment(把公用的代码写在里面,比如检测网络、弹出alertdialog等等)
 9 定制一个Application类代替默认的(很多第三方框架需要把一些代码写到定制的Application类里面)
10 消息推送(比如友盟)
11 用户反馈(比如友盟)
12 数据统计(比如友盟)
13 更新(比如友盟)
14 数据备份和恢复
15 点赞、评论、收藏模块
16 About界面(版权申明+常用软件设置+版本更新+国际化等)
17 在线crash log上报(比如腾讯的bugly)
18 快速开发框架(这里推荐使用butterknife和eventbus)
19 内存泄漏检测工具(leakcanary)
20 图片加载库(Glide)
21 加密解密(RSA,MD5,DES)
22 带有删除的EditText
23 定制下拉加载的控件
24 listview/recyclerview的基础adapter
25 定制搜索框
26 工具类(比如sharepreference,File,ScreenDesity,Sql,字符串处理,dpsp互转等等)
27 底部加载更多
28 自定义alertdialog等对话框(可以写在BaseActivity中)
29 通用的popupwindow
30 快速返回顶部按钮
31 Toolbar代替actionbar
32 各种新式的Material design兼容控件
33 界面滑动冲突的问题(横竖冲突、同向冲突)
34 离线登录功能
35 bitmap缓存策略
36 最后项目要发布了,那么久需要混淆和打包了,前者关于混淆网上有很多相关的文章,这里需要注意的是很多你所使用的第三方库都需要在混淆的时候给剔除,因为他们是基于反射机制的。这点需要你在使用每个第三方库的时候多看他们的说明书多加小心。其次,混淆后一定要打个包重新回归测试一下,以免出现因混淆而导致的问题。
其他待分析模块

 

1. 启动页面 - SplashActivity

From: Android学习系列(35)--App应用之启动界面SplashActivity

控件:进度条,广告页面,文字描述

功能:版本检测【不需要登录】

实现:异步执行任务 - AsyncTask【重点讲解】

 

优秀资源:

From: ViksaaSkool/AwesomeSplash【效果华丽】

From: tutorialwing.com【模块代码齐全】

From: codepath/android_guides【功能代码较齐全】

  

2. 登陆与注册

3. 忘记密码/修改密码

参考界面:android-firebase-chat【登录效果华丽】

仅限邮件登录。 

 

 

 

专题:AsyncTask --> 异步线程


一、回顾:[Android] 01 - Java Basic for Android - Java 多线程编程

1. 实现接口

class RunnableDemo implements Runnable {  
public void run() {   // 运行状态     ... } } RunnableDemo R1 = new RunnableDemo( "Thread-1"); R1.start();  // ----> 进入就绪状态

2. 继承类 

class ThreadDemo extends Thread {

   public void run() {   // ----> 重写该类的run()方法,线程的执行体
      ... ...
   }
   
   public void start() {
      ... ...
} }

3. Callable和Future,一个产生结果,一个拿到结果。 

4. Java5之后的策略 - Executor框架

 

 

二、Handler认识

Ref: Android基础夯实--你了解Handler有多少?

背景

Handler是用来结合线程的消息队列来发送、处理“Message对象”和“Runnable对象”的工具。

每一个Handler实例之后会关联一个线程和该线程的消息队列。

  1. 当你创建一个Handler的时候,从这时开始,它就会自动关联到所在的线程/消息队列,
  2. 然后它就会陆续把Message/Runnalbe分发到消息队列,并在它们出队的时候处理掉。

谷歌采用了只允许在主线程更新UI,所以作为线程通信桥梁的Handler也就应运而生了。

 

四者关系

  • Looper

每一个线程只有一个Looper,每个线程 initialize Looper,

Looper会维护该线程的消息队列,用来存放Handler发送的Message,并处理消息队列出队的Message。

跟线程是绑定的,处理消息也是在Looper所在的线程去处理,

所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。

在主线程中,ActivityThread默认会把Looper初始化好,prepare以后,当前线程就会变成一个Looper线程。

  • MessageQueue

MessageQueue是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。

MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象在创建时将自动创建一个MessageQueue;其他非主线程,不会自动创建Looper。

  • Message

消息对象,就是MessageQueue里面存放的对象,一个MessageQueue可以包括多个Message。

当我们需要发送一个Message时,我们一般不建议使用new Message()的形式来创建,更推荐使用Message.obtain()来获取Message实例:

因为在Message类里面定义了一个消息池,

    • 当消息池里存在未使用的消息时,便返回,
    • 如果没有未使用的消息,则通过new的方式创建返回,

所以使用Message.obtain()的方式来获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。

  • Handler

推送未来某个时间点将要执行的Message或者Runnable到消息队列。

在子线程把需要在另一个线程执行的操作加入到消息队列中去。

 

用途一

在主线程中,可以通过Handler来处理一些有顺序的操作,让它们在固定的时间点被执行。

Handler处理有序事件的用途,分开用Message and Runnable两种方法来实现,最终都只是将Message推送到MessageQueue

只要大家看一下源码就可以知道,Handler的post Runnable对象这个方法只是对post Message进行了一层封装,所以最终我们都是通过Handler推送了一个Message罢了。

 

方法一,通过Handler + Message的方式实现倒计时。

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;

    private Handler mHandler ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //通过Handler + Message的方式实现倒计时
                for (int i = 1; i <= 10; i++) {
                    Message message = Message.obtain(mHandler);
                    message.what = 10 - i;
                    mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息
                }
            }
        });

        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                mBinding.time.setText(msg.what + "");   //在handleMessage中处理消息队列中的消息
            }
        };
    }
}

 

方法二,通过Handler + Runnable的方式实现倒计时。

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for (int i = 1; i <= 10; i++) {
                    final int fadedSecond = i;
                    //每延迟一秒,发送一个Runnable对象
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mBinding.time.setText((10 - fadedSecond) + "");
                        }
                    }, 1000 * i);
                }
            }
        });
    }
}

 

用途二

通过Handler + Message来实现子线程加载图片,在UI线程显示图片。 

public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
    private ActivityThreadBinding mBinding = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
        // 设置点击事件
        mBinding.clickBtn.setOnClickListener(this);
        mBinding.resetBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            // 响应load按钮
            case R.id.clickBtn:

                new Thread(new Runnable() {    // <---- 子线程任务完成后要将结果(图片)交给主线程更新UI
                    @Override
                    public void run() {
                        // 在Runnable中进行网络读取操作,返回bitmap
                        final Bitmap bitmap = loadPicFromInternet();
// 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可 Handler handler = new Handler(Looper.getMainLooper());  // <---- 将Handler关联到任意一个线程的Looper
// 通过Handler的post Runnable到UI线程的MessageQueue中去即可 handler.post(new Runnable() {   // ----> 入队了 @Override public void run() { mBinding.photo.setImageBitmap(bitmap);  // <---- 出队时才操作 } }); } }).start(); break; case R.id.resetBtn: mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic)); break; } }


  public Bitmap loadPicFromInternet() {
    ...
  }

 

Handler引发的内存泄漏

在上面的例子中,为了展示方便,我都没有考虑内存泄漏的情况,但是在实际开发中,如果不考虑代码的安全性的话,尤其当一个项目到达了一定的规模之后,那么对于代码的维护和系统的调试都是非常困难的。而Handler的内存泄漏在Android中也是一个非常经典的案例。

详细可以参考:How to Leak a Context: Handlers & Inner Classes

或参考翻译文:Android中Handler引起的内存泄露

 

 /* implement */

 

 

HandlerThread

Ref: Android常用异步任务执行方法

HandlerThread,就是一种线程;和普通的Thread相比,HandlerThread在创建时,会提供给自己对应的Looper对象。毕竟只有主线程才有默认的Looper。

new Thread(){
  @Override
  public void run() {
    super.run();
    Looper.prepare();  // 非主线程只能自己创建
    Handler mHandler = new Handler(Looper.myLooper());
    Looper.loop();
  }
}.start();

所以,HandlerThread就在内部帮我们封装了Looper的创建过程,帮助我们轻松创建一个包含了Looper的子线程

final HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
mHandlerThread.start();
Handler mHandler = new Handler(mHandlerThread.getLooper());

 

使用HandlerThread同时下载A和B的Demo:  

public class HandlerThreadActivity extends AppCompatActivity {
    private TextView tv_A, tv_B;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);

        tv_A = (TextView) findViewById(R.id.txt_dlA);
        tv_B = (TextView) findViewById(R.id.txt_dlB);

        final Handler mainHandler = new Handler();

    /////////////////////////////////////////////////////////////////////////////////////////
final HandlerThread downloadAThread = new HandlerThread("downloadAThread"); downloadAThread.start();
Handler downloadAHandler
= new Handler(downloadAThread.getLooper()); downloadAHandler.postDelayed(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), "下载A完成", Toast.LENGTH_SHORT).show(); mainHandler.post(new Runnable() { @Override public void run() { tv_A.setText("A任务已经下载完成"); } }); } }, 1000 * 5);     /////////////////////////////////////////////////////////////////////////////////////////
final HandlerThread downloadBThread = new HandlerThread("downloadBThread"); downloadBThread.start();
Handler downloadBHandler
= new Handler(downloadBThread.getLooper()); downloadBHandler.postDelayed(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), "下载B完成", Toast.LENGTH_SHORT).show(); mainHandler.post(new Runnable() { @Override public void run() { tv_B.setText("B任务已经下载完成"); } }); } }, 1000 * 7);
} }

 

 

三、AsyncTask认识

Android的消息机制主要靠Handler来实现,但是在Handler的使用中,忽略内存泄露的问题,不管是代码量还是理解程度上都显得有点不尽人意,所以Google官方帮我们在Handler的基础上封装出了AsyncTask。

参考资料:

AsyncTask你真的用对了吗?

AsyncTask和Handler的优缺点比较

AsyncTask的缺陷和问题

android多线程-AsyncTask之工作原理深入解析(上) 

android多线程-AsyncTask之工作原理深入解析(下) 

 

 

 

 

posted @ 2018-03-28 16:41  郝壹贰叁  阅读(295)  评论(0)    收藏  举报