【转】Android 开发之ActivityLifecycleCallback

转载自:Android 开发之ActivityLifecycleCallback


一. 简介

ActivityLifecycleCallbacks是Application里的一个接口,在Android4.0(API level 14)中新加。用于监听应用中所有Activity的生命周期的调用情况,callback中的方法会在Activity回调方法之前调用,可以通过实现这个接口来完成一些特殊需求,比如检测APP是否处于前台。

二. 使用

以检测APP是否处于前台为例介绍使用。

1. 实现ActivityLifecycleCallbacks接口

public class ActivityLifecycleCallbackWrapper implements Application.ActivityLifecycleCallbacks {
    private static final String TAG = "LifecycleCallback";
    private int count; 
    private boolean isForeground; 

    public boolean isForeground() {
        return isForeground; 
    }

    @Override 
    public void onActivityCreated(Activity activity, Bundle bundle) {
        //to do 
    }

    @Override 
    public void onActivityStarted(Activity activity) { 
        count ++; 
    }

    @Override 
    public void onActivityResumed(Activity activity) {
        //to do 
    }

    @Override 
    public void onActivityPaused(Activity activity) {
        //to do 
    }

    @Override 
    public void onActivityStopped(Activity activity) { 
        count --; 
        if(count == 0) {
            isForeground = true; 
        }
    }

    @Override 
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
        //to do 
    }

    @Override 
    public void onActivityDestroyed(Activity activity) {
        //to do 
    }
}

2. 创建自定义Application,并在Application中注册callback。

public class MyApplication extends Application {
    ActivityLifecycleCallbacks callbacks; 

    @Override 
    public void onCreate() {
        super.onCreate(); 
        callbacks = new ActivityLifecycleCallbackWrapper();

        registerActivityLifecycleCallbacks(callbacks);  // 注册Callback
    }
}

利用反射获取ViewBinding

ViewBindingUtil

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.viewbinding.ViewBinding;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;

public class ViewBindingUtil {

    public static <Binding extends ViewBinding> Binding create(Class<?> clazz, LayoutInflater inflater) {
        return create(clazz, inflater, null);
    }

    public static <Binding extends ViewBinding> Binding create(Class<?> clazz, LayoutInflater inflater, ViewGroup root) {
        return create(clazz, inflater, root, false);
    }

    @SuppressWarnings("unchecked")
    @NonNull
    public static <Binding extends ViewBinding> Binding create(Class<?> clazz, LayoutInflater inflater, ViewGroup root, boolean attachToRoot) {
        Class<?> bindingClass = getBindingClass(clazz);
        Binding binding = null;
        if (bindingClass != null) {
            try {
                Method method = bindingClass.getMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
                binding = (Binding) method.invoke(null, inflater, root, attachToRoot);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return Objects.requireNonNull(binding);
    }

    private static Class<?> getBindingClass(Class<?> clazz) {
        ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
        Type[] types = Objects.requireNonNull(parameterizedType).getActualTypeArguments();
        Class<?> bindingClass = null;
        for (Type type : types) {
            if (type instanceof Class<?>) {
                Class<?> temp = (Class<?>) type;
                if (ViewBinding.class.isAssignableFrom(temp)) {
                    bindingClass = temp;
                }
            }
        }
        return bindingClass;
    }
}

BaseActivity

import android.os.Bundle;
import android.view.LayoutInflater;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewbinding.ViewBinding;

public abstract class BaseActivity<Binding extends ViewBinding> extends AppCompatActivity {
    protected Binding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ViewBindingUtil.create(getClass(), LayoutInflater.from(this));
        setContentView(binding.getRoot());
    }
}

参考链接:

利用ViewBinding和反射封装的基类,从此再也不用findViewById了

配置国内阿里云 Gradle 仓库镜像

选择一:项目级配置(推荐)

在项目根目录的build.gradle文件中添加以下内容

buildscript {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google/' }
        maven { url 'https://maven.aliyun.com/repository/public/' }
    }
}

allprojects {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google/' }
        maven { url 'https://maven.aliyun.com/repository/public/' }
    }
}

继续阅读配置国内阿里云 Gradle 仓库镜像

是时候拥抱ViewBinding了!!

节选自:是时候拥抱ViewBinding了!!


三、拥抱ViewBinding

关于ViewBinding的文档,官方写的很详细,请看视图绑定。本文一切从简,主要说下Google官方没有提到的一些问题。

3.1、环境要求

  • Android Studio版本3.6及以上
  • Gradle 插件版本3.6.0及以上

3.2、开启ViewBinding功能

ViewBinding支持按模块启用,在模块的build.gradle文件中添加如下代码:

android {
        ...
        viewBinding {
            enabled = true
        }
}

3.3、Activity中ViewBinding的使用

//之前设置视图的方法
setContentView(R.layout.activity_main);

//使用ViewBinding后的方法
mBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());

继续阅读是时候拥抱ViewBinding了!!

Android单元测试-常见的方案比较

节选自:Android单元测试-常见的方案比较


前言

本文将介绍在Android Studio中,android单元测试的介绍和实现。相关代码托管在github上的AndroidJunitDemo中,涉及到的用例代码收集于google官方提供的测试用例android-testing,同时进行了简化和修改。你可以从该demo中学习单元测试简单的使用,在工程中,包含两个模块,一个实现计算器功能的CalculationActivity,另外一个是PersonlInfoActivity,可以编辑姓名,邮箱和生日等信息,并保存到SharePreferences中,同时提供了两个模块的单元测试。

单元测试

关于单元测试,在维基百科中,给出了如下定义:

在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

android中的单元测试基于JUnit,可分为本地测试和instrumented测试,在项目中对应

  • module-name/src/test/java/.
    该目录下的代码运行在本地JVM上,其优点是速度快,不需要设备或模拟器的支持,但是无法直接运行含有android系统API引用的测试代码。
  • module-name/src/androidTest/java/.
    该目录下的测试代码需要运行在android设备或模拟器下面,因此可以使用android系统的API,速度较慢。

以上分别执行在JUnit和AndroidJUnitRunner的测试运行环境,两者主要的区别在于是否需要android系统API的依赖。
在实际开发过程中,我们应该尽量用JUnit实现本地JVM的单元测试,而项目中的代码大致可分为以下三类:

  • 1.强依赖关系,如在Activity,Service等组件中的方法,其特点是大部分为private方法,并且与其生命周期相关,无法直接进行单元测试,可以进行Ecspreso等UI测试。
  • 2.部分依赖,代码实现依赖注入,该类需要依赖Context等android对象的依赖,可以通过Mock或其它第三方框架实现JUnit单元测试或使用androidJunitRunner进行单元测试。
  • 3.纯java代码,不存在对android库的依赖,可以进行JUnit单元测试

Android最简单的LoadingDialog

节选自:Android最简单的LoadingDialog


Activity的基类

public class BaseAcitivity extends Activity {

    private AlertDialog alertDialog;

    public void showLoadingDialog() {
        alertDialog = new AlertDialog.Builder(this).create();
        alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable());
        alertDialog.setCancelable(false);
        alertDialog.show();
        alertDialog.setContentView(R.layout.loading_alert);
        alertDialog.setCanceledOnTouchOutside(false);
    }

    public void dismissLoadingDialog() {
        if (null != alertDialog && alertDialog.isShowing()) {
            alertDialog.dismiss();
        }
    }
}

XML:loading_alert

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <ProgressBar
        style="@style/AppTheme.NoActionBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_gravity="center_horizontal" />

</RelativeLayout>

在styles.xml中添加

<style name="AppTheme.NoActionBar">
    <item name="android:windowActionBar">false</item>
    <item name="android:windowNoTitle">true</item>
</style>

安卓-连续点击两次返回键退出程序-二级界面的退出程序

节选自:安卓-连续点击两次返回键退出程序-二级界面的退出程序


法一:实现方式,通过记录按键时间计算时间差实现:

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.Toast;

public class MainActivity extends Activity {

    private long exitTime = 0;

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

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            exit();
            return false;
        }
        return super.onKeyDown(keyCode, event);
    }

    public void exit() {
        if ((System.currentTimeMillis() - exitTime) > 2000) {
            Toast.makeText(getContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show();
            exitTime = System.currentTimeMillis();
        } else {
            finish();
            System.exit(0);
        }
    }
}

Android获取相机最佳预览大小和获取屏幕宽高

/**
 * 获取最佳预览大小
 *
 * @param parameters       相机参数
 * @param screenResolution 屏幕宽高
 * @return
 */
private Point getBestCameraResolution(Camera.Parameters parameters, Point screenResolution) {
    float tmp;
    float mindiff = 100f;
    float x_d_y = (float) screenResolution.x / (float) screenResolution.y;
    Size best = null;
    List<Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
    for (Size s : supportedPreviewSizes) {
        tmp = Math.abs(((float) s.height / (float) s.width) - x_d_y);
        if (tmp < mindiff) {
            mindiff = tmp;
            best = s;
        }
    }
    return new Point(best.width, best.height);
}

/**
 * 获取屏幕宽度和高度,单位为px
 *
 * @param context
 * @return
 */
public static Point getScreenMetrics(Context context) {
    DisplayMetrics dm = context.getResources().getDisplayMetrics();
    return new Point(dm.widthPixels, dm.heightPixels);
}