本篇文章给大家谈谈java–最佳实践:输入验证(Android),以及java输入校验原则的知识点,同时本文还将给你拓展AndroidAPT(编译时代码生成)最佳实践、AndroidDebug之Log
本篇文章给大家谈谈java – 最佳实践:输入验证(Android),以及java输入校验原则的知识点,同时本文还将给你拓展Android APT(编译时代码生成)最佳实践、Android Debug 之 Log 最佳实践、Android Studio JSOUP 最佳实践、android – 在片段和活动之间进行通信 – 最佳实践等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:- java – 最佳实践:输入验证(Android)(java输入校验原则)
- Android APT(编译时代码生成)最佳实践
- Android Debug 之 Log 最佳实践
- Android Studio JSOUP 最佳实践
- android – 在片段和活动之间进行通信 – 最佳实践
java – 最佳实践:输入验证(Android)(java输入校验原则)
我是Android移动开发的新手(Android Studio原生开发 – 用于新知识).在这里,我想问一个关于输入验证的最佳实践的问题.
据我们所知,当开发人员开发输入表单时.我们需要阻止用户输入错误的输入文本字段.所以这是我的问题,
>我们可以创建一个java文件仅用于验证目的吗?所有输入表单,只能转到那一个验证文件(如果一个应用程序中有多个输入页面屏幕).如果是,我怎样才能为我的学习研究获得该技术的示例/链接/教程.如果不是,为什么?
从我个人的角度来看,它应该有一种实现该技术的方法.因此,我们不需要为每个java文件重新使用相同的代码(根据干净的代码).不幸的是,我没有找到任何示例或教程.也许我搜索错误的关键字或误读.如果不存在这样的技术,输入验证的最佳实践是什么?
谢谢.
p / s:这个帖子可以在最佳实践中找到更好的方法.谢谢.
解决方法:
这个java类实现了一个TextWatcher来“监视”你的编辑文本,观察对文本所做的任何更改:
public abstract class TextValidator implements TextWatcher {
private final TextView textView;
public TextValidator(TextView textView) {
this.textView = textView;
}
public abstract void validate(TextView textView, String text);
@Override
final public void afterTextChanged(Editable s) {
String text = textView.getText().toString();
validate(textView, text);
}
@Override
final public void
beforeTextChanged(CharSequence s, int start, int count, int after) {
/* Needs to be implemented, but we are not using it. */
}
@Override
final public void
onTextChanged(CharSequence s, int start, int before, int count) {
/* Needs to be implemented, but we are not using it. */
}
}
在EditText中,您可以将该文本观察器设置为其侦听器
editText.addTextChangedListener(new TextValidator(editText) {
@Override public void validate(TextView textView, String text) {
/* Insert your validation rules here */
}
});
Android APT(编译时代码生成)最佳实践
越来越多第三方库使用apt技术,如DBflow、Dagger2、ButterKnife、ActivityRouter、AptPreferences。在编译时根据Annotation生成了相关的代码,非常高大上但是也非常简单的技术,可以给开发带来了很大的便利。
Annotation
如果想学习APT,那么就必须先了解Annotation的基础,这里附加我另外一篇文章的地址: Java Annotation 基础教程
APT
APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
创建Annotation Module
首先,我们需要新建一个名称为annotation的Java Library,主要放置一些项目中需要使用到的Annotation和关联代码。这里简单自定义了一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test { }
配置build.gradle,主要是规定jdk版本
apply plugin: ''java''
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile fileTree(dir: ''libs'', include: [''*.jar''])
}
创建Compiler Module
创建一个名为compiler的Java Library,这个类将会写代码生成的相关代码。核心就是在这里。
配置build.gradle
apply plugin: ''java''
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
compile fileTree(dir: ''libs'', include: [''*.jar''])
compile ''com.google.auto.service:auto-service:1.0-rc2''
compile ''com.squareup:javapoet:1.7.0''
compile project('':annotation'')
}
- 定义编译的jdk版本为1.7,这个很重要,不写会报错。
- AutoService 主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息。
- JavaPoet 这个库的主要作用就是帮助我们通过类调用的形式来生成代码。
- 依赖上面创建的annotation Module。
定义Processor类
生成代码相关的逻辑就放在这里。
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(Test.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
生成第一个类
我们接下来要生成下面这个HelloWorld的代码:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
修改上述TestProcessor的process方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
在app中使用
配置项目根目录的build.gradle
dependencies {
classpath ''com.neenbedankt.gradle.plugins:android-apt:1.8''
}
配置app的build.gradle
apply plugin: ''com.android.application''
apply plugin: ''com.neenbedankt.android-apt''
//...
dependencies {
//..
compile project('':annotation'')
apt project('':compiler'')
}
编译使用
在随意一个类添加@Test注解
@Test
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
点击Android Studio的ReBuild Project,可以在在app的 build/generated/source/apt
目录下,即可看到生成的代码。
基于注解的View注入:DIActivity
到目前我们还没有使用注解,上面的@Test也没有实际用上,下面我们做一些更加实际的代码生成。实现基于注解的View,代替项目中的findByView
。这里仅仅是学习怎么用APT,如果真的想用DI框架,推荐使用ButterKnife,功能全面。
- 第一步,在annotation module创建@DIActivity、@DIView注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView {
int value() default 0;
}
- 创建DIProcessor方法
@AutoService(Processor.class)
public class DIProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public Set<String> getSupportedAnnotationTypes() {
// 规定需要处理的注解
return Collections.singleton(DIActivity.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("DIProcessor");
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DIActivity.class);
for (Element element : elements) {
// 判断是否Class
TypeElement typeElement = (TypeElement) element;
List<? extends Element> members = elementUtils.getAllMembers(typeElement);
MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.VOID)
.addParameter(ClassName.get(typeElement.asType()), "activity");
for (Element item : members) {
DIView diView = item.getAnnotation(DIView.class);
if (diView == null){
continue;
}
bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s) activity.findViewById(R.id.text)",item.getSimpleName(),ClassName.get(item.asType()).toString()));
}
TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
.superclass(TypeName.get(typeElement.asType()))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(bindViewMethodSpecBuilder.build())
.build();
JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private String getPackageName(TypeElement type) {
return elementUtils.getPackageOf(type).getQualifiedName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}
- 使用DIActivity
@DIActivity
public class MainActivity extends Activity {
@DIView(R.id.text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DIMainActivity.bindView(this);
textView.setText("Hello World!");
}
}
实际上就是通过apt生成以下代码
public final class DIMainActivity extends MainActivity {
public static void bindView(MainActivity activity) {
activity.textView = (android.widget.TextView) activity.findViewById(R.id.text);
}
}
常用方法
常用Element子类
- TypeElement:类
- ExecutableElement:成员方法
- VariableElement:成员变量
通过包名和类名获取TypeName
TypeName targetClassName = ClassName.get("PackageName", "ClassName");
通过Element获取TypeName
TypeName type = TypeName.get(element.asType());
获取TypeElement的包名
String packageName = processingEnv.getElementUtils().getPackageOf(type).getQualifiedName().toString();
获取TypeElement的所有成员变量和成员方法
List<? extends Element> members = processingEnv.getElementUtils().getAllMembers(typeElement);
总结
推荐阅读dagger2、dbflow、ButterKnife等基于apt的开源项目代码。JavaPoet 也有很多例子可以学习。
Example代码
https://github.com/taoweiji/DemoAPT
我们的开源项目推荐:
Android快速持久化框架:AptPreferences
AptPreferences是基于面向对象设计的快速持久化框架,目的是为了简化SharePreferences的使用,减少代码的编写。可以非常快速地保存基本类型和对象。AptPreferences是基于APT技术实现,在编译期间实现代码的生成,根据不同的用户区分持久化信息。
https://github.com/joyrun/AptPreferences
ActivityRouter路由框架:通过注解实现URL打开Activity
基于apt技术,通过注解方式来实现URL打开Activity功能,并支持在WebView和外部浏览器使用,支持多级Activity跳转,支持Bundle、Uri参数注入并转换参数类型。
https://github.com/joyrun/ActivityRouter
Android Debug 之 Log 最佳实践
本文微信公众号「AndroidTraveler」首发。
背景
在开发过程中,调试是必不可少的一项工作。
当我们要确定项目的逻辑时,当我们要了解界面的生命周期时,当我们发现新写的逻辑与期望效果不一致时,当我们觉得数据有问题时......
而调试有两种方式:
第一种就是使用 debug 模式运行 APP,然后通过断点让程序运行到指定位置进行分析。
第二种就是打日志的方式,通过观察输出来确定程序是否运行到该位置以及此时的数据。
本篇文章主要聚焦在第二种方式上面。
在 Android 里面,打日志使用的系统 API 是 Log,你以为直接使用就完了吗?
封装
假设你在需要打印日志的地方直接使用系统的 API,那么当遇到下面情况时,会「牵一发而动全身」。
场景一:如果我打印日志要用三方库的日志 API,那么我要查找项目所有使用位置,并一一替换。
场景二:如果我希望在开发环境下打印日志,release 环境不打印,这个时候每个位置都需要单独做处理。
因此我们需要在使用 Log 进行日志打印之前,做一层封装。
假设我们的类名字为 ZLog,代码如下:
import android.util.Log;
/**
* Created on 2019-10-26
*
* @author Zengyu.Zhan
*/
public class ZLog {
public static int v(String tag, String msg) {
return Log.v(tag, msg);
}
public static int d(String tag, String msg) {
return Log.d(tag, msg);
}
public static int i(String tag, String msg) {
return Log.i(tag, msg);
}
public static int w(String tag, String msg) {
return Log.w(tag, msg);
}
public static int e(String tag, String msg) {
return Log.e(tag, msg);
}
}
这样处理之后,对于场景一和场景二,我们需要修改的只是 ZLog 这个类,而不需要到具体使用 ZLog 的所有地方去修改。
提供日志打印控制
我们知道,日志打印可能包含敏感信息,而且过多的日志打印可能影响 APP 的性能,因此我们一般是在开发时候打开日志,在发布 APP 之前关闭。
因此我们这边需要提供一个标志位来控制日志的打印与否。
import android.util.Log;
/**
* Created on 2019-10-26
*
* @author Zengyu.Zhan
*/
public class ZLog {
private static boolean isDebugMode = false;
public static void setDebugMode(boolean debugMode) {
isDebugMode = debugMode;
}
public static int v(String tag, String msg) {
return isDebugMode ? Log.v(tag, msg) : -1;
}
public static int d(String tag, String msg) {
return isDebugMode ? Log.d(tag, msg) : -1;
}
public static int i(String tag, String msg) {
return isDebugMode ? Log.i(tag, msg) : -1;
}
public static int w(String tag, String msg) {
return isDebugMode ? Log.w(tag, msg) : -1;
}
public static int e(String tag, String msg) {
return isDebugMode ? Log.e(tag, msg) : -1;
}
}
默认是不开启日志打印,避免开发者忘记设置。
普通日志和奔溃栈系统日志在控制台的输出对比
现在我们在 APP 里面使用 ZLog 打印日志,代码为:
ZLog.setDebugMode(true);
ZLog.e("ZLog", "just test");
输出如下:
我们现在增加如下代码:
String nullString = null;
if (nullString.equals("null")) {
}
运行之后控制台会显示空指针异常奔溃栈,如下:
可以看到奔溃栈信息会显示具体是哪个文件出现了空指针,以及具体哪一行。在我们这个例子里面就是 MainActivity.java 的 24 行。
而且点击蓝色链接光标会直接定位到错误位置。
如果我们普通的日志也可以点击就跳转到对应位置,对于我们开发来说效率是有很大提升的。
ZLogHelper
既然奔溃栈里面有链接可以跳转,那么我们可以通过栈信息来获取日志的打印位置。
我们直接上代码:
public class ZLogHelper {
private static final int CALL_STACK_INDEX = 1;
private static final Pattern ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$");
public static String wrapMessage(int stackIndex, String message) {
// DO NOT switch this to Thread.getCurrentThread().getStackTrace().
if (stackIndex < 0) {
stackIndex = CALL_STACK_INDEX;
}
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
if (stackTrace.length <= stackIndex) {
throw new IllegalStateException(
"Synthetic stacktrace didn''t have enough elements: are you using proguard?");
}
String clazz = extractClassName(stackTrace[stackIndex]);
int lineNumber = stackTrace[stackIndex].getLineNumber();
message = ".(" + clazz + ".java:" + lineNumber + ") - " + message;
return message;
}
/**
* Extract the class name without any anonymous class suffixes (e.g., {@code Foo$1}
* becomes {@code Foo}).
*/
private static String extractClassName(StackTraceElement element) {
String tag = element.getClassName();
Matcher m = ANONYMOUS_CLASS.matcher(tag);
if (m.find()) {
tag = m.replaceAll("");
}
return tag.substring(tag.lastIndexOf(''.'') + 1);
}
}
这里我们对外提供一个 wrapMessage 方法,看名字就知道是对 Message 进行包装。
方法里面也是对 StackTraceElement 进行分析。
这边还做了一个控制,避免 stackIndex 出现负数情况。
可能有小伙伴会好奇,为什么要把 stackIndex 对外开放呢?
因为你打印日志的地方不一样,这里的 stackIndex 也需要对应调整。
方法里面是对 StackTraceElement 做处理,而 StackTraceElement 跟你的方法层级有关系。
我们以最常用的两种日志打印形式为例,来说明这里的 stackIndex 要怎么传递,以及这个 ZLogHelper 的用法。
直接代码使用
我们在 MainActivity.java 中直接使用,stackIndex 传入 1 即可。
Log.e("ZLog", ZLogHelper.wrapMessage(1, "just test"));
控制台输出如下: 可以看到代码所在的类和行数到显示为链接文本,点击会定位到具体的位置。
做了封装的情况
一般我们对 Log 都会做封装,因此假设我们有一个 LogUtils 类,我们在 MainActivity.java 里面调用。
LogUtils.java:
class LogUtils {
public static void loge() {
Log.e("ZLog", ZLogHelper.wrapMessage(2, "just test"));
}
}
MainActivity.java:
LogUtils.loge();
我们先看下结果,再来分析。控制台输出如下:
可以看到确实定位到了 MainActivity.java 中的具体使用地方。
那么为什么这里传入的 stackIndex 跟第一种不一样,是 2 而不是 1 呢?
其实答案很简单,你改为 1 之后,输出的控制台显示的会定位到 LogUtils 里面的日志打印语句处。在这里就是:
Log.e("ZLog", ZLogHelper.wrapMessage(2, "just test"));
所以其实你可以看出一个规律,而这个从代码也可以发现。
因为代码里面解析调用位置是根据栈来的,对 StackTraceElement 进行分析,因此情况一直接使用,传入 1。而情况二多了一层函数调用,通过 loge 方法做了一层包装。因此需要传入 2。如果你再套一层,那么需要传入 3。了解了这一点,我们下面的工具类相信你就看得懂了。
ZLog
如果你不想自己手动传入 stackIndex,可以直接使用我们提供的工具类 ZLog。
public class ZLog {
private static boolean isDebugMode = false;
public static void setDebugMode(boolean debugMode) {
isDebugMode = debugMode;
}
private static boolean isLinkMode = true;
public static void setLinkMode(boolean linkMode) {
isLinkMode = linkMode;
}
private static final int CALL_STACK_INDEX = 3;
public static int v(String tag, String msg) {
return isDebugMode ? Log.v(tag, mapMsg(msg)) : -1;
}
public static int d(String tag, String msg) {
return isDebugMode ? Log.d(tag, mapMsg(msg)) : -1;
}
public static int i(String tag, String msg) {
return isDebugMode ? Log.i(tag, mapMsg(msg)) : -1;
}
public static int w(String tag, String msg) {
return isDebugMode ? Log.w(tag, mapMsg(msg)) : -1;
}
public static int e(String tag, String msg) {
return isDebugMode ? Log.e(tag, mapMsg(msg)) : -1;
}
private static String mapMsg(String msg) {
return isLinkMode ? ZLogHelper.wrapMessage(CALL_STACK_INDEX, msg) : msg;
}
}
相信有了前面的知识,小伙伴对于这里为什么传入 3 应该了解了。
1 的话会定位到
return isLinkMode ? ZLogHelper.wrapMessage(CALL_STACK_INDEX, msg) : msg;
2 的话 (以 e 为例) 会定位到
return isDebugMode ? Log.e(tag, mapMsg(msg)) : -1;
3 的话才能够定位到外面具体的调用处。
优化
我们知道,虽然 ZLog 做了封装,但是我们每次打日志都要传入 ZLog,有点麻烦?
能否提供一个默认的 TAG,允许对外设置。
可以的,我们修改如下 (以 e 为例):
private static String tag = "ZLOG";
public static void setTag(String tag) {
if (!TextUtils.isEmpty(tag)) {
ZLog.tag = tag;
}
}
public static int e(String tag, String msg) {
return isDebugMode ? Log.e(mapTag(tag), mapMsg(msg)) : -1;
}
public static int e(String msg) {
return isDebugMode ? Log.e(tag, mapMsg(msg)) : -1;
}
private static String mapTag(String tag) {
return TextUtils.isEmpty(tag) ? ZLog.tag : tag;
}
项目实战
按照下面两步引入开源库。
Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url ''https://jitpack.io'' }
}
}
Step 2. Add the dependency
dependencies {
implementation ''com.github.nesger:AndroidWheel:1.0.0''
}
使用时先打开开关:
ZLog.setDebugMode(true);
然后就可以直接使用了。
温馨提示
由于带链接的 debug 对性能有一定影响,因此建议开发使用,上线关闭。
结语
这边在完善一个开源仓库 AndroidWheel,跟名字一样,避免重复造轮子。
目前 1.0.0 版本提供日志相关工具类,1.0.1 增加了防抖动 EditText。
后续会继续更新迭代,功能会更完善更全面。
觉得不错,欢迎给个 star 哈~
参考链接:
Android Studio Pro Tip: go to source from logcat output
原文出处:https://www.cnblogs.com/nesger/p/11880128.html
Android Studio JSOUP 最佳实践
如何解决Android Studio JSOUP 最佳实践
我用 Java 编写应用程序。在我的应用程序中,我有很多带有 JSOUP 的 AsyncTasks。但是 AsyncTask 已被弃用,所以我发现了很多变化的变体:
- 凌空抽射
- RxJava
- 主题
- 执行者
- 协程
我想问一下 JSOUP 而不是 AsyncTask 的最佳选择是什么?
android – 在片段和活动之间进行通信 – 最佳实践
这个问题主要是征求关于处理我的应用程序的最佳方式的意见.我有三个片段由一个活动处理.片段A有一个可点击元素照片,片段B有4个可点击元素按钮.另一个片段只显示单击照片时的详细信息.我正在使用ActionBarSherlock.
前进和后退按钮需要分别将照片更改为下一个或上一个姿势.我可以将照片和按钮保持在相同的片段中,但是想要将它们分开,以防我想在平板电脑中重新排列它们.
我需要一些建议 – 我应该将碎片A和B结合起来吗?如果没有,我将需要弄清楚如何为3个可点击项目实现一个界面.
我考虑过使用Roboguice,但我已经在使用SherlockFragmentActivity进行扩展,所以这是不行的.我看到提到otto,但我没有看到关于如何包含在项目中的好教程.您认为最佳设计实践应该是什么?
我还需要帮助找出如何在片段和活动之间进行通信.我想在应用程序中保留一些“全局”数据,比如姿势ID.除了股票开发者的信息之外,我还能看到一些示例代码吗?这不是那么有用.
顺便说一下,我已经在sqlite数据库中存储了每个姿势的所有信息.这很容易.
解决方法:
在您的活动和片段之间进行通信的最简单方法是使用接口.这个想法基本上是在给定片段A中定义一个接口,让活动实现该接口.
一旦它实现了该接口,您可以在它覆盖的方法中执行任何您想要的操作.
界面的另一个重要部分是您必须从片段中调用抽象方法并记住将其强制转换为您的活动.如果没有正确完成,它应该捕获ClassCastException.
关于如何完成这种事情,有关于Simple Developer Blog的一个很好的教程.
我希望这对你有所帮助!
关于java – 最佳实践:输入验证(Android)和java输入校验原则的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于Android APT(编译时代码生成)最佳实践、Android Debug 之 Log 最佳实践、Android Studio JSOUP 最佳实践、android – 在片段和活动之间进行通信 – 最佳实践的相关知识,请在本站寻找。
本文标签: