#Android-简单应用编写

😘5ea1


前面已经有一篇博客写安卓初探了,结构什么的也没什么好说的,直接开造

EzLog

创建项目的时候选择空项目,然后语言选择java

布局文件

跟qt一样,AndroidStudio中的布局文件也可以使用可视化的方式构建,首先在res资源文件夹下创建/layout/activity_main.xml作为布局文件,打开后点击右上角切换图片设计视图
1
这样就可以从组件盒中拿取组件进行拼接了
注意,此处要使用androidx.constraintlayout.widget.ConstraintLayout进行构建,如果使用另外两个,组件可能会堆积在左上角或某一条边上

因为要实现一个简单的登录应用,所以这里我只使用了两个EditView输入框和一个Button按钮,其中有一个输入框用了密码模板,输入的时候自动变成黑点,且做了输入hint。

为了确保组件位置,还需要对组件进行定位,此处可以使用手动定位,也可以使用上方便捷工具进行推测定位
2
3

主程序

完成了布局的定义,此时可以直接编写主程序调用这几个组件了

button = findViewById(R.id.button);
editTextText = findViewById(R.id.editTextText);
editTextTextPassword = findViewById(R.id.editTextTextPassword);

此处通过id的方式寻找所要的组件

因为登录操作需要按下按钮,所以此处设置一个侦听事件来检查按钮有没有被按下,若按下了,则执行内部函数

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                String name = editTextText.getText().toString();
                String password = editTextTextPassword.getText().toString();
                String correctUsername = getString(R.string.correct_username);
                String correctPassword = getString(R.string.correct_password);

                if (name.equals(correctUsername) && password.equals(correctPassword))
                {
                    showRightDialog(MainActivity.this);
                }
                else
                {
                    showRetryDialog(MainActivity.this);
                }
            }

        });

这里使用了getText()函数从string.xml资源文件中读取账号和密码,再使用.equal函数来进行比较,若正确,则执行函数showRightDialog();反之则执行showRetryDialog()

private void showRetryDialog(Context context)
    {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("验证失败")
                .setMessage("再试试")
                .setPositiveButton("确定", null)
                .show();
    }
    private void showRightDialog(Context context)
    {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("正确")
                .setMessage("恭喜")
                .setPositiveButton("确定", null)
                .show();
    }

这两个函数就是写在同一文件下的函数

运行

运行后得到一个朴素的主界面,可以输入账号密码尝试登录
4
5
6


#Calc

写一个简单而单纯的计算器

布局

这个应用最繁琐的步骤就是设计这个布局文件,计算器嘛,键肯定少不了,要是限制定死了,不一样的设备间距一变,肯定就死了。

7

关键点:

  1. 最左侧键按照最左侧边定位,中间的左右定位,最右侧不要和左侧定位
  2. 设置text框的时候要注意是Edit的还是Text,和后续读取有关

主程序

只需要设置大量的按钮触发事件,代码就和一个普通控制台计算器一样了。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        //老样子,先把所有的组件打印出来
        TextView TextviewIn = findViewById(R.id.textViewInput);
        TextView TextviewOut = findViewById(R.id.textViewOutput);

        Button button0 = findViewById(R.id.buttonNumber0);
        Button button1 = findViewById(R.id.buttonNumber1);
        Button button2 = findViewById(R.id.buttonNumber2);
        Button button3 = findViewById(R.id.buttonNumber3);
        Button button4 = findViewById(R.id.buttonNumber4);
        Button button5 = findViewById(R.id.buttonNumber5);
        Button button6 = findViewById(R.id.buttonNumber6);
        Button button7 = findViewById(R.id.buttonNumber7);
        Button button8 = findViewById(R.id.buttonNumber8);
        Button button9 = findViewById(R.id.buttonNumber9);
        Button buttonand = findViewById(R.id.buttonAnd);
        Button buttoninc = findViewById(R.id.buttonInc);
        Button buttondiv = findViewById(R.id.buttonDiv);
        Button buttonchen = findViewById(R.id.buttonChen);
        Button buttonxor = findViewById(R.id.buttonXor);
        Button buttondot = findViewById(R.id.buttonDot);
        Button buttoncalc = findViewById(R.id.buttonCalc);
        Button buttonc = findViewById(R.id.buttonC);

        ButtonClickListener clickListener = new ButtonClickListener(this, TextviewIn, TextviewOut);

        button0.setOnClickListener(clickListener);
        button1.setOnClickListener(clickListener);
        button2.setOnClickListener(clickListener);
        button3.setOnClickListener(clickListener);
        button4.setOnClickListener(clickListener);
        button5.setOnClickListener(clickListener);
        button6.setOnClickListener(clickListener);
        button7.setOnClickListener(clickListener);
        button8.setOnClickListener(clickListener);
        button9.setOnClickListener(clickListener);
        buttonand.setOnClickListener(clickListener);
        buttoninc.setOnClickListener(clickListener);
        buttonchen.setOnClickListener(clickListener);
        buttondiv.setOnClickListener(clickListener);
        buttonxor.setOnClickListener(clickListener);
        buttondot.setOnClickListener(clickListener);
        buttoncalc.setOnClickListener(clickListener);
        buttonc.setOnClickListener(clickListener);
        //设置了大量的按钮相关组件
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
    }

这里为每个按钮设置了一个监听,来保证不同的按钮触发不同事件,所以按钮事件的监听器需要重写


ButtonClickListener重写

public class ButtonClickListener implements View.OnClickListener {

    private Context context;
    private TextView Textview;
    private TextView TextViewOutput;

    public ButtonClickListener(Context context, TextView Textview,TextView TextViewOutput) {
        this.context = context;
        this.Textview = Textview;
        this.TextViewOutput= TextViewOutput;
    }

    @Override
    public void onClick(View v) {
        String currentText = Textview.getText().toString();
        if (currentText.equals("0") && v.getId() != R.id.buttonDot && v.getId() != R.id.buttonCalc) {
            currentText = "";
        }

        if (v.getId() == R.id.buttonNumber0) {
            if (!currentText.isEmpty()) {
                Textview.setText(currentText + "0");
            } else {
                Textview.setText("0");
            }
        }
        if (v.getId() == R.id.buttonNumber0) {
            Textview.setText(currentText + "0");
        } else if (v.getId() == R.id.buttonNumber1) {
            Textview.setText(currentText + "1");
        } else if (v.getId() == R.id.buttonNumber2) {
            Textview.setText(currentText + "2");
        } else if (v.getId() == R.id.buttonNumber3) {
            Textview.setText(currentText + "3");
        } else if (v.getId() == R.id.buttonNumber4) {
            Textview.setText(currentText + "4");
        } else if (v.getId() == R.id.buttonNumber5) {
            Textview.setText(currentText + "5");
        } else if (v.getId() == R.id.buttonNumber6) {
            Textview.setText(currentText + "6");
        } else if (v.getId() == R.id.buttonNumber7) {
            Textview.setText(currentText + "7");
        } else if (v.getId() == R.id.buttonNumber8) {
            Textview.setText(currentText + "8");
        } else if (v.getId() == R.id.buttonNumber9) {
            Textview.setText(currentText + "9");
        } else if (v.getId() == R.id.buttonAnd) {
            Textview.setText(currentText + "+");
        } else if (v.getId() == R.id.buttonInc) {
            Textview.setText(currentText + "-");
        } else if (v.getId() == R.id.buttonDiv) {
            Textview.setText(currentText + "/");
        } else if (v.getId() == R.id.buttonChen) {
            Textview.setText(currentText + "*");
        } else if (v.getId() == R.id.buttonXor) {
            Textview.setText(currentText + "^");
        } else if (v.getId() == R.id.buttonC) {
            Textview.setText("0");
            TextViewOutput.setText("");
        } else if (v.getId() == R.id.buttonDot) {
            if (currentText.isEmpty() || currentText.charAt(currentText.length() - 1) != '.') {
                Textview.setText(currentText + ".");
            }
        } else if (v.getId() == R.id.buttonCalc) {
            if (isValidExpression(currentText)) {
                try{
                    Toast.makeText(Textview.getContext(), "计算", Toast.LENGTH_SHORT).show();
                    double result = CalculatorUtils.calculateExpression(currentText);
                    TextViewOutput.setText(String.valueOf(result));
                } catch (Exception e) {
                    Textview.setText("0");
                    Toast.makeText(Textview.getContext(), "计算错误", Toast.LENGTH_SHORT).show();
                }
            } else {
                Textview.setText("0");
                TextViewOutput.setText("");
                Toast.makeText(Textview.getContext(), "输入不合法", Toast.LENGTH_SHORT).show();
            }
        }
    }
    private boolean isValidExpression(String expression) {
        if (expression.isEmpty()) {
            return false;
        }

        char lastChar = expression.charAt(expression.length() - 1);
        if (lastChar == '+' || lastChar == '-' || lastChar == '*' || lastChar == '/' || lastChar == '^') {
            return false;
        }

        // 新增:检查连续运算符
        for (int i = 0; i < expression.length() - 1; i++) {
            char currentChar = expression.charAt(i);
            char nextChar = expression.charAt(i + 1);
            if ((currentChar == '+' || currentChar == '-' || currentChar == '*' || currentChar == '/' || currentChar == '^') &&
                    (nextChar == '+' || nextChar == '-' || nextChar == '*' || nextChar == '/' || nextChar == '^')) {
                return false;
            }
        }

        int dotCount = 0;
        for (int i = 0; i < expression.length(); i++) {
            if (expression.charAt(i) == '.') {
                boolean hasDigitBeforeDot = false;
                boolean hasDigitAfterDot = false;

                for (int j = i - 1; j >= 0; j--) {
                    if (Character.isDigit(expression.charAt(j))) {
                        hasDigitBeforeDot = true;
                        break;
                    }
                }

                for (int j = i + 1; j < expression.length(); j++) {
                    if (Character.isDigit(expression.charAt(j))) {
                        hasDigitAfterDot = true;
                        break;
                    }
                }
                if (!hasDigitBeforeDot || (!hasDigitAfterDot && i != expression.length() - 1)) {
                    return false;
                }
            }
        }
        return true;
    }
}

这段代码主要实现了两个功能

  1. 用了大量的if-else语句来为不同的button-id情况分别讨论(实际上,这里可以使用一些更加简练的表达方式)
  2. 写了一个验证器,来判断输入的数据是否合法,防止不合法的输入传入本地库中

可以看到,在读入calc按钮事件(即计算事件)的时候,代码中调用了一个函数来计算


CalculatorUtils

调用的函数属于CalculatorUtils类中,而这个类的代码很简单,就是访问本地库,从库中拿代码

package com.example.calc;

public class CalculatorUtils {
    static {
        System.loadLibrary("calc");
    }

    public static native double calculateExpression(String expression);
}

calc.cpp

即平常说的.so文件,c++本地库

#include <jni.h>
#include <stack>
#include <string>
#include <cmath>

// 定义运算符优先级
int precedence(char op) {
    if (op == '+' || op == '-')
        return 1;
    if (op == '*' || op == '/')
        return 2;
    if (op == '^')
        return 3;
    return 0;
}

// 应用运算符
double applyOp(double a, double b, char op) {
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return a / b;
        case '^': return std::pow(a, b);
    }
    return 0;
}

// 计算表达式
extern "C" JNIEXPORT jdouble JNICALL
Java_com_example_calc_CalculatorUtils_calculateExpression(JNIEnv *env, jobject /* this */, jstring expressionStr) {
    const char *expression = env->GetStringUTFChars(expressionStr, nullptr);
    std::stack<double> values;
    std::stack<char> ops;

    for (int i = 0; expression[i] != '\0'; i++) {
        if (expression[i] == ' ')
            continue;
        else if (isdigit(expression[i]) || expression[i] == '.') {
            std::string valStr;
            while (i < strlen(expression) && (isdigit(expression[i]) || expression[i] == '.')) {
                valStr += expression[i++];
            }
            i--;
            values.push(std::stod(valStr));
        } else if (expression[i] == '(') {
            ops.push(expression[i]);
        } else if (expression[i] == ')') {
            while (!ops.empty() && ops.top() != '(') {
                double val2 = values.top();
                values.pop();
                double val1 = values.top();
                values.pop();
                char op = ops.top();
                ops.pop();
                values.push(applyOp(val1, val2, op));
            }
            if (!ops.empty())
                ops.pop();
        } else {
            while (!ops.empty() && precedence(ops.top()) >= precedence(expression[i])) {
                double val2 = values.top();
                values.pop();
                double val1 = values.top();
                values.pop();
                char op = ops.top();
                ops.pop();
                values.push(applyOp(val1, val2, op));
            }
            ops.push(expression[i]);
        }
    }

    while (!ops.empty()) {
        double val2 = values.top();
        values.pop();
        double val1 = values.top();
        values.pop();
        char op = ops.top();
        ops.pop();
        values.push(applyOp(val1, val2, op));
    }
 
    env->ReleaseStringUTFChars(expressionStr, expression);
    return values.top();
}

实现了计算器的计算式分割和计算功能


测试

8
9

EzCalc

写一个简单的计算器,但真的只是计算器吗

项目考虑使用上面的计算器源码,加一个Crypto模块

构筑

在源代码中,在应用层获取到的数据会以字符串的形式传输到本地层so库中,可以考虑在此处加一个侦听模块,当侦听到特定的输入的时候,开放解密模块
解密模块采取某种解密形式,将用户输入要计算的字符串进行解密,解密后不进行输出,直接加到缓存里面,逆向者可以从侦听应用的缓存,来获取解密得到的flag

解密模块