热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

十一、安卓系统上的调试和测试

十一、安卓系统上的调试和测试在本章中,您将学习如何在安卓系统中进行调试,这是在开发我们的应用时节

十一、安卓系统上的调试和测试

在本章中,您将学习如何在安卓系统中进行调试,这是在开发我们的应用时节省发现和修复问题的时间的基本做法。

我们将学习如何创建可以测试按钮点击或单一方法结果的自动化测试。这是一组你可以在安卓工作室运行的测试,以确保每次你开发一个新功能时,你不会破坏任何现有的功能。

您还将学习如何使用机器人进行单元测试,以及如何使用 Espresso 进行集成测试。

在这一章的最后,我们将讨论如何使用 Monkey 测试具有数百万次随机点击的 UI,如何通过应用记录点击序列,以及如何使用 MonkeyTalk 基于这些记录配置测试。


  • 日志和调试模式

  • 测试

    • 用机器人进行单元测试

    • 浓缩咖啡的整合测试



  • 用户界面测试

    • 使用 MonkeyRunner 随机点击

    • 用 MonkeyTalk 录制点击



  • 连续累计


日志和调试模式

不提到日志,不提到开发时如何调试解决问题,我们就完不成这本书。如果你知道如何解决自己的问题,在安卓系统中开发不仅仅是从堆栈溢出中复制和粘贴。

调试模式和日志是用来帮助开发人员识别问题所在的机制。随着时间的推移,每个开发人员改进和使用这些技术的频率都降低了,但在开始时,让应用充满日志是很常见的。我们不希望用户在应用发布时能够看到日志,也不希望在发布新版本时手动删除日志后再重新添加。我们将看看如何避免这种情况。

使用日志

日志类是用来打印出消息和错误,我们可以使用LogCat实时读取。这是一个如何记录消息的示例:

Log.i("MyActivity", "Example of an info log");

Log类有五种方法,它们用于在日志上具有不同的优先级。这允许我们在LogCat中按优先级过滤。有些情况下,我们会显示不同的日志,例如,查看我们在每个请求中下载的工作机会的数量。如果我们的应用崩溃了,此时错误类型的日志是我们的优先级,我们希望隐藏其他优先级较低的日志,以便尽快找到错误。

五个优先级是(从低到高)详细、调试、信息、警告和错误。(Log.vLog.dLog.iLog.wLog.e)

我们可以通过日志窗口顶部的栏按进程进行过滤。我们可以按优先级和关键字过滤,也可以按标签、进程标识等创建自定义过滤器。

Working with logs

如果日志没有出现或者旧的不刷新,请尝试打开右侧的下拉菜单,选择 过滤器,然后再次选择仅显示选定的应用。这将强制控制台刷新。

Working with logs

最后,我们将创建一个包装器,并使用第三方库,其思想是只需更改一个布尔值就可以禁用项目中的所有日志。为此,我们只需用依赖于该布尔值的Log类的相同方法创建一个类:

public class MyLogger {
static final boolean LOG = false;
public static void i(String tag, String string) {
if (LOG) android.util.Log.i(tag, string);
}
public static void e(String tag, String string) {
if (LOG) android.util.Log.e(tag, string);
}

每次我们想写日志的时候都需要使用这个包装器——MyLogger.d()而不是Log.d()。这样,如果我们改变MyLogger类中布尔LOG的值,它将同时停止我们项目中的所有日志。

建议使用BuildConfing.DEBUG变量的值:

static final boolean LOG = BuildConfing.DEBUG;

如果我们的应用处于调试模式,这将是真的,当我们发布应用时,这将是假的。所以,我们不需要记得在发布模式下关闭日志,也没有日志出现在最终用户面前的风险。

使用木材,原木包装

木材是杰克·沃顿(Jake Wharton)创建的一个日志包装器,它将日志提升到了一个高级水平,允许我们使用日志树概念来拥有不同的日志行为。看看下面的代码:

compile 'com.jakewharton.timber:timber:3.1.0'

使用木材的优势之一是,在同一活动中,我们不需要在日志中多次写入标签:

Timber.tag("LifeCycles");
Timber.d("Activity Created");

我们的树可以有不同的行为;例如,我可能想在发布模式下禁用日志,但我仍然想处理错误;因此,我将植入一个错误树,向 Parse 报告错误:

if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
} else {
Timber.plant(new CrashReportingTree());
}
/** A tree which logs important information for crash reporting. */
private static class CrashReportingTree extends Timber.Tree {
@Override protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return;
}
//Track error to parse.com
}
}


调试我们的应用

日志在开发的时候可以用来发现问题,但是如果掌握了调试模式,我们会发现这种做法要快很多。

当我们处于调试模式时,我们可以在代码中设置断点。有了这些断点,我们指定一行代码,我们希望在那里停止执行,以向我们显示当时的变量值。要设置断点,只需双击左侧栏:

Debugging our app

我们在获得工作机会的方法的响应中设置了一个调试点。我们可以从顶部的栏启动调试模式:

Debugging our app

如果我们在调试模式下运行该应用,安卓工作室将在达到这一点时暂停执行:

Debugging our app

安卓工作室会自动提示调试器窗口,在这里我们可以看到执行点的变量。我们可以在前面的截图中看到工作邀请列表,并导航查看每个邀请里面都有什么。

这里重要的功能是左边绿色的 Play 按钮,继续执行我们的 app,直到下一个断点,红色的方块,退出调试模式,继续执行 app。

我们还有不同的控件可以移动到下一行、方法中或方法外。例如,假设我们在以下命令的第一行有一个断点:

MasteringAndroidDAO.getInstance().clearDB(getActivity());
MasteringAndroidDAO.getInstance().storeOffers(getActivity(), jobOffersList);

在这种情况下, Step Over ,也就是向下的蓝色箭头,将把我们的执行移到下一行。如果我们点击进入,指向右下角的蓝色箭头,我们将进入方法。结合这些控制,我们可以实时控制流量。

解释了调试模式后,我们现在可以继续进行自动化测试了。

在安卓上测试

一个新的功能没有经过测试是不完整的。作为开发人员,我们已经多次陷入提交代码更改而没有先编写一个通过测试的陷阱,结果却发现预期的行为在未来的迭代中被打破了。

我们学到了编写测试提高生产力、提高代码质量和帮助我们更频繁地发布的艰难方法。出于这个原因,安卓提供了几个工具来帮助我们从早期测试我们的应用。

在接下来的两个部分中,我们将讨论我最喜欢的设置,用于单元测试的 Robolectric 和用于集成测试的 Espresso。

用电机进行单元测试

直到机器人,编写单元测试意味着我们必须在一个真实的设备或模拟器上运行它们。这个过程可能需要几分钟,因为安卓构建工具必须打包测试代码,将其推送到连接的设备,然后运行它。

Robolectric 使我们能够在工作站的 JVM 中运行单元测试,而不需要安卓设备或仿真器,从而缓解了这个问题。

为了包含使用 Gradle 的 Robolectric,我们可以将以下依赖项添加到我们的build.gradle文件中:

testCompile "org.robolectric:robolectric:3.0"

我们使用testCompile来指定我们希望这个依赖关系包含在我们的测试项目中。对于测试项目,默认的源目录是src/test

机器人配置

在撰写之时,Robolectric 版本支持以下安卓 SDK:


  • 果冻豆,SDK 第 16 版

  • 果冻豆 MR1,SDK 第 17 版

  • 果冻豆 MR2,SDK 第 18 版

  • KitKat,SDK 第 19 版

  • 棒棒糖,SDK 第 21 版

默认情况下,测试将针对AndroidManifest文件中定义的targetSdkVersion运行。如果您想要针对不同的 SDK 版本运行测试,或者如果您当前的targetSdkVersion不受 Robolectric 的支持,您可以使用位于src/test/resources/robolectric.properties的属性文件手动覆盖它,该文件包含以下内容:

robolectric.properties
sdk=<SDK_VERSION>


我们的第一次单元测试

我们将从开始设置一个非常简单和常见的场景:一个带有登录按钮的欢迎活动,该按钮将用户导航到登录活动。欢迎活动的布局如下:

xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:id="@+id/login" android:text="Login" android:layout_width="wrap_content" android:layout_height="wrap_content" />
LinearLayout>

WelcomeActivity类中,我们将简单地设置登录按钮来开始登录活动:

public class WelcomeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.welcome_activity);
View button = findViewById(R.id.login);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(WelcomeActivity.this, LoginActivity.class));
}
});
}
}

为了测试这一点,我们可以通过发送正确的Intent来确保我们启动LoginActivity。因为 Robolectric 是一个单元测试框架,LoginActivity实际上不会启动,但是我们将能够检查该框架是否捕获了正确的意图。

首先,我们将在src/test/java/路径内的正确包中创建测试文件WelcomeActivityTest.java。robo electric 依赖于 JUnit 4,因此我们将从指定 robo electric 的 Gradle 测试运行程序和一些额外的配置开始,框架将使用这些配置来查找AndroidManifest资源和资产。运行以下命令:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)

现在,我们可以写我们的第一个测试。我们将从创建欢迎活动并将其带到前台开始:

WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);

现在我们有了WelcomeActivity的实例,点击登录按钮就很容易了:

activity.findViewById(R.id.login).performClick();

最后,我们必须验证框架捕捉到了开始LoginActivity的意图:

Intent expectedIntent = new Intent(activity, LoginActivity.class);
assertThat(shadowOf(activity).getNextStartedActivity(), is(equalTo(expectedIntent)));

shadowOf静态方法返回一个ShadowActivity对象,该对象存储了与测试中的当前活动的大部分交互。我们需要使用@Test注释,它告诉 JUnit 该方法可以作为测试用例运行。综合起来,我们有以下测试类(WelcomeActivityTest.java):

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class WelcomeActivityTest {
@Test
public void loginPress_startsLoginActivity() {
WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);
activity.findViewById(R.id.login).performClick();
Intent expectedIntent = new Intent(activity, LoginActivity.class);
assertThat(shadowOf(activity).getNextStartedActivity(), is(equalTo(expectedIntent)));
}
}


运行单元测试

在能够运行单元测试之前,我们需要在安卓工作室中选择正确的测试神器。为此,我们将打开构建变体工具栏,并选择单元测试工件,如下图所示:

Running unit tests

现在,从项目窗口,我们可以通过右键单击测试类并选择运行选项来运行测试。确保项目路径中没有空格;否则,Robolectric 将在执行单元测试之前抛出异常。

Running unit tests

我们也可以从命令行运行单元测试。为此,使用--continue选项调用test任务命令:

./gradlew test --continue

如果我们配置了持续集成系统,例如 Jenkins、Travis 或 wercker,那么这个选项是理想的。

这是机器人部分的结尾。接下来,我们将讨论与浓缩咖啡的集成测试。

浓缩咖啡的整合测试

由于安卓的“T0”特性和大量的“T1”设备,我们永远无法确定应用在发布时的行为。

我们自然倾向于在尽可能多的不同设备上手动测试我们的应用,这是一个繁琐的过程,我们必须在每次发布时重复。在本节中,我们将简要讨论 Espresso,以及如何编写将在真实设备上运行的测试。

浓缩咖啡配置

在编写我们的第一个集成测试之前,我们需要安装并配置我们的测试环境。请执行以下步骤:


  1. From Android SDK Manager, we need to select and install Android Support Repository from the Extras folder, as shown in the following screenshot:

    Espresso configuration


  2. 为我们的集成测试代码创建文件夹;这应该位于app/src/androidTest


  3. 我们还需要在项目的build.gradle中指定一些依赖项。使用以下代码:

    java
    dependencies {
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test:rules:0.3'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
    androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2'
    }


最近,Android 增加了对 JUnit 4 风格测试用例的支持。为此,我们将在build.gradle文件中添加AndroidJUnitRunner作为默认测试仪器运行程序:

android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}


写整合测试

对于这个例子,我们将从我们停止使用机器人的地方继续;我们将为LoginActivity写一个测试。对于本活动,我们将设置一个简单的布局,带有两个EditTexts和一个登录按钮。运行以下代码(activity_login.xml):

xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/input_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/input_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
<Button
android:id="@+id/button_signin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/signin"/>
LinearLayout>

LoginActivity中,当用户点击登录按钮时,我们将使用以下代码(LoginActivity.java)向启动活动发送凭据:

public class LoginActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
final EditText inputUsername = (EditText) findViewById(R.id.input_username);
final EditText inputPassword = (EditText) findViewById(R.id.input_password);
Button buttonLogin = (Button) findViewById(R.id.button_signin);
buttonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(), SplashActivity.class)
.putExtra("username", inputUsername.getText().toString())
.putExtra("password", inputPassword.getText().toString()));
finish();
}
});
}
}

对于这个测试,我们将在两个输入字段中键入用户凭证,并验证我们在意图中正确地绑定了它们。

首先,我们将在src/test/androidTest/路径内的正确包中创建LoginActivityTest.java测试文件。我们将使用 JUnit 4,所以我们将从指定AndroidJUnit4测试运行器开始。使用以下命令:

@RunWith(AndroidJUnit4.class)

另一个区别是,在浓缩咖啡中,我们需要指定一个规则来准备测试活动。为此,请使用以下命令:

@Rule
public IntentsTestRule<LoginActivity> mActivityRule =
new IntentsTestRule<>(LoginActivity.class);

现在,我们可以开始写测试了。首先,我们需要在两个输入字段中输入登录详细信息:

String expectedUsername = "mastering@android.com";
String expectedPassword = "electric_sheep";
onView(withId(R.id.input_username)).perform(typeText(expectedUsername));
onView(withId(R.id.input_password)).perform(typeText(expectedPassword));

然后,我们将通过点击登录按钮发送意向:

onView(withId(R.id.button_signin)).perform(click());

最后,我们必须验证捕获的意图包含登录凭据:

intended(hasExtras(allOf(
hasEntry(equalTo("username"), equalTo(expectedUsername)),
hasEntry(equalTo("password"), equalTo(expectedPassword)))));

将一切放在一起,我们将有以下测试类(LoginActivityTest.java):

@RunWith(AndroidJUnit4.class)
public class LoginActivityTest {
@Rule
public IntentsTestRule<LoginActivity> mActivityRule =
new IntentsTestRule<>(LoginActivity.class);
@Test
public void loginButtonPressed_sendsLoginCredentials() {
String expectedUsername = "mastering@android.com";
String expectedPassword = "electric_sheep";
onView(withId(R.id.input_username)).perform(typeText(expectedUsername));
onView(withId(R.id.input_password)).perform(typeText(expectedPassword));
onView(withId(R.id.button_signin)).perform(click());
intended(hasExtras(allOf(
hasEntry(equalTo("username"), equalTo(expectedUsername)),
hasEntry(equalTo("password"), equalTo(expectedPassword)))));
}
}


运行集成测试

类似于我们对 Robolectric 所做的,为了运行集成测试,我们需要切换到 Android Studio 中正确的测试工件。为此,我们将打开构建变体工具栏,并选择安卓仪器测试神器:

Running integration tests

现在,从 项目窗口,我们可以通过右键单击测试类并选择运行选项来运行测试。

我们也可以从命令行运行集成测试。为此,我们将调用connectedCheck(或cC)任务:

./gradlew cC

如果我们有一个带有连接设备或仿真器的持续集成系统,使用命令行是首选方式。一旦我们编写了足够的集成测试,我们就可以使用 Testdroid 等服务,在数百个真实设备上轻松部署和运行它们。

Running integration tests

从用户界面角度进行测试

我们现在要做的测试类似于使用该应用的人可以做的那种测试。事实上,在拥有 QA ( 质量保证)的公司,人们使用这些工具作为人工测试的补充。

UI 测试也可以自动化,但是它们不同于单元测试和集成测试;这些都是在屏幕上执行的操作,从点击按钮到用记录的事件完成注册过程。

我们将从猴子的压力测试开始。

发射猴子

猴子是一个可以通过 ADB 从命令行启动的程序。它在我们的设备或模拟器中生成随机事件,使用种子,我们可以重现相同的随机事件。为了澄清,让我们考虑一个有数字的例子。想象一下,我执行 Monkey,它产生从 1 到 10 的随机数;如果我再次推出它,我会得到不同的数字。当我用一个种子(这个种子是一个数字)执行猴子时,我得到一组从 1 到 10 的不同数字,如果我用同一个种子再次启动它,我将得到相同的数字。这很有用,因为如果我们使用种子生成随机事件并发生崩溃,我们可以修复此崩溃并再次运行相同的种子,以确保我们修复了问题。

这些随机事件可以从点击和滚动手势到系统级事件(如调高音量、调低音量、截屏等)不等。我们可以限制事件的数量和类型以及运行这些事件的包。

终端中的基本语法是以下命令:

$ adb shell monkey [options] <event-count>

如果你从未使用过 ADB,你可以在它里面找到 Android SDK 目录内的platform-tools文件夹,无论它安装在你的系统的什么地方:

../sdk/platform-tools/adb

当我们打开一个终端并导航到这个目录时,我们可以写下下面一行代码:

adb shell monkey -p com.packtpub.masteringandroidapp -v 500

当尝试使用adb且输出为command not found时,如果使用 Linux 或 Mac,可以用adb kill-serveradb start-serveruse ./adb ( 点斜线 adb )重启adb

我们可以将事件的数量增加到5000或者产生无限个事件,但总是建议设置数量的限制;否则,你将不得不重启设备来停止猴子。当您执行该命令时,您将能够看到产生的随机事件,并且它将指示在您想要重复相同的事件链时使用的种子:

Launching The Monkey

根据应用的不同,我们可能需要使用 throttle 毫秒属性调整事件之间的时间,以便模拟真实用户。

使用下一个测试工具,我们将进行不同类型的用户界面测试,目的是遵循流程。例如,如果我们有一个由三个不同表单的屏幕组成的注册过程,并且想要记录一个测试,其中用户填写表单并逻辑地继续通过这三个屏幕。在这种情况下,猴子不会真的帮忙;事件数量非常多,它最终会用随机字符完成所有的输入字段,并点击按钮移动到下一个屏幕,但这并不是我们想要的。

用 MonkeyTalk 记录用户界面测试

记录一系列测试(如注册过程)的目的是保存这个测试,以便在我们更改代码时能够再次运行它。我们可能不得不修改注册过程的网络请求,而不改变用户界面,所以这些测试是完美的。我们可以在完成修改后运行它们,而不必手动完成注册或自己填写表格。我们在这里不是懒惰;如果我们有数百个测试,这对一个人来说将是很大的努力。同样,通过自动化测试,我们可以确保事件的顺序总是相同的。

MonkeyTalk 是一款免费开源工具,有两个版本;我们将使用社区版本作为示例。

比较社区版和专业版的列表可以在他们位于https://www.cloudmonkeymobile.com/monkeytalk的网站上看到。

MonkeyTalk 可以在真实设备和仿真器上使用。它的工作原理是在我们处于记录模式时记录事件列表:

Recording UI tests with MonkeyTalk

一旦我们通过点击工具中的记录进入记录模式,每个事件都将按顺序记录,并记录执行的动作和使用的参数。在前面的截图中,我们可以看到点击TextView和在上面写一些输入是如何被记录为两个事件的。

我们可以在一个脚本文件中创建它,MonkeyTalk 会重现它;因此,我们可以选择创建自己的事件序列,而无需记录。对于前面的事件,我们将编写如下脚本:

Input username tap
Input username enterText us

如果我们点击立即播放按钮,我们将看到所有这些步骤在任何设备上执行。我们可以在安卓手机上录制脚本,然后在 iOS 设备上播放。

除了录制和播放脚本,我们还可以有验证命令。例如,如果我们有一个清除所有输入字段的按钮,我们可以在脚本中使用currentValue添加一个验证命令:

Input username tap
Input username enterText us
Input clearform click
Input currentvalue ""

这将在执行过程中报告验证的结果,因此我们将能够检查我们的所有验证是否正确通过。例如,点击按钮来清除表单需要一个点击监听器来清除所有输入文本。如果出于某种原因,我们进行了修改,并且元素的标识发生了变化,那么 MonkeyTalk 测试将报告命令验证失败的问题。

每当我们在应用中进行更改时,有一个工具为我们运行这些 UI 测试,以及单元和集成测试,这不是很好吗?这个解决方案是存在的,叫做持续集成

持续整合

解释如何构建一个持续集成系统并不是我们的意图,因为这不在本书的讨论范围内,设置环境通常也不是安卓开发者的工作。然而,你应该知道它是什么,它是如何工作的,因为它与安卓直接相关。

一套好的自动化测试总是更好地与 CI 或持续集成解决方案相结合。这个解决方案将允许我们在每次代码更改时构建和测试我们的应用。

这是大多数有大项目的公司的工作方式。如果他们有一个开发团队,代码通常在一个存储库中共享,他们构建一个连接到存储库的 CI 系统。每次开发人员对存储库进行更改并提交时,都会执行测试集合,如果结果成功,则会构建一个新的 Android 可执行文件( APK )。

这样做是为了将出现问题的风险降至最低。在一个大的应用中,需要不同的人来开发它,一个新的开发人员不可能在不破坏或改变任何现有功能的情况下开始做出改变。这是因为不是项目中的所有人都知道所有代码的目的,或者代码太复杂了,修改一个组件会改变其他组件。

如果您有兴趣实施此解决方案,我们可以向您推荐 Jenkins ,其最初在https://wiki.jenkins-ci.org/display/JENKINS/Meet+Jenkins名为 Hudson。

Continuous integration

除了测试和构建我们的应用之外,Jenkins 还将生成一个测试覆盖报告,这将允许我们知道单元和集成测试覆盖的代码的百分比。

总结

在这一章中,我们开始学习如何在我们的应用中以高级方式使用日志,并快速概述了调试过程。我们解释了什么是测试,以及如何分别使用 Robolectric 和 Espresso 创建单元测试和集成测试。

我们还创建了 UI 测试,首先用 The Monkey 进行压力测试,然后生成随机事件,之后用 MonkeyTalk 开始测试,记录可以再次播放的事件流,验证输出。最后,我们谈到了持续集成,以发现公司如何为安卓应用整合测试和构建系统。

在下一章,也就是本书的最后一章,我们将看看如何将我们的应用货币化,如何使用不同的构建风格构建应用,以及混淆代码,让它准备好上传到应用商店。


推荐阅读
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了如何清除Eclipse中SVN用户的设置。首先需要查看使用的SVN接口,然后根据接口类型找到相应的目录并删除相关文件。最后使用SVN更新或提交来应用更改。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
author-avatar
手机用户2602915211
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有