程序結構如下:
一、在app模塊添加依賴
//動態權限
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar'
//二維碼掃描庫
implementation 'com.google.zxing:core:3.2.1'
implementation 'cn.bingoogolapple:bga-qrcodecore:1.1.9@aar'
implementation 'cn.bingoogolapple:bga-zxing:1.1.7@aar'
//Rx庫
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.3.8'
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.6.0'
二、在清單AndroidManifest.xml中添加權限
<!--網絡訪問-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--攝像頭-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--振動器-->
<uses-permission android:name="android.permission.VIBRATE"/>
三、網絡訪問會有問題,需要在res下創建xml\network_security_config.xml文件
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
<!--允許開啓http請求-->
然後在AndroidManifest.xml中的application節點下添加:
android:networkSecurityConfig="@xml/network_security_config"
四、主界面佈局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/ll_user"
android:layout_marginTop="200dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用戶名: " />
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密 碼: " />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_below="@id/ll_user"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<Button
android:id="@+id/btn_app_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="APP登錄" />
<Button
android:id="@+id/btn_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="二維碼掃描" />
</LinearLayout>
</RelativeLayout>
主要就顯示用戶App端登錄的界面,加一個跳轉到二維碼掃描的按鈕。
五、主程序 MainActivity.java
package com.chris.base.scanloginapp;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Button btnLogin, btnScan;
private EditText etUsername,etPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
btnLogin = findViewById(R.id.btn_app_login);
btnScan = findViewById(R.id.btn_scan);
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
etUsername.setText("chenfabao");
etPassword.setText("123456");
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
//SharedPreferences sp = getSharedPreferences("loginUser",MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
String username = etUsername.getText().toString().trim();
edit.putString("username", username);
String password = etPassword.getText().toString().trim();
edit.putString("password", password);
edit.commit();
//setTitle(username + " : " + password);
Toast.makeText(MainActivity.this, "APP登陸成功", Toast.LENGTH_SHORT).show();
}
});
btnScan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, ScanActivity.class));
}
});
}
}
六、掃碼界面 activity_scan.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ScanActivity">
<cn.bingoogolapple.qrcode.zxing.ZXingView
android:id="@+id/zxv_qrcode_scan"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:qrcv_borderSize="0dp"
app:qrcv_cornerColor="#3F4259"
app:qrcv_isShowDefaultScanLineDrawable="true"
app:qrcv_isShowTipTextAsSingleLine="true"
app:qrcv_isTipTextBelowRect="true"
app:qrcv_maskColor="#80000000"
app:qrcv_qrCodeTipText="將二維碼放入框內,即可自動掃描"
app:qrcv_rectWidth="240dp"
app:qrcv_scanLineColor="#44DB5E"
app:qrcv_tipTextColor="#FFFFFF"
app:qrcv_tipTextSize="16sp" />
</RelativeLayout>
七、掃碼登錄邏輯 ScanActivity.java
package com.chris.base.scanloginapp;
import android.Manifest;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.tbruyelle.rxpermissions.RxPermissions;
import cn.bingoogolapple.qrcode.core.QRCodeView;
import cn.bingoogolapple.qrcode.zxing.ZXingView;
import rx.functions.Action1;
public class ScanActivity extends AppCompatActivity implements QRCodeView.Delegate {
private ZXingView zXingView;
private String username, password;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan);
getUserInfo();
initView();
}
private void getUserInfo() {
//SharedPreferences sp = getSharedPreferences("loginUser",MODE_PRIVATE);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
username = sp.getString("username", "unkown");
password = sp.getString("password", "unkown");
Toast.makeText(ScanActivity.this, username + " : " + password, Toast.LENGTH_LONG).show();
setTitle(username + " : " + password);
}
private void initView() {
zXingView = findViewById(R.id.zxv_qrcode_scan);
zXingView.setDelegate(this);
if (Build.VERSION.SDK_INT >= 23) {
RxPermissions.getInstance(this)
.request(Manifest.permission.CAMERA)
.subscribe(new Action1<Boolean>() {
@Override
public void call(Boolean aBoolean) {
if (aBoolean) {
zXingView.startCamera();
zXingView.startSpotDelay(500);
Toast.makeText(ScanActivity.this, "6.0獲取權限成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(ScanActivity.this, "權限被拒絕", Toast.LENGTH_SHORT).show();
finish();
}
}
});
} else {
Toast.makeText(ScanActivity.this, "權限被拒絕", Toast.LENGTH_SHORT).show();
zXingView.startCamera();
zXingView.startSpotDelay(500);
}
}
@Override
public void onScanQRCodeSuccess(final String result) {
Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
//振動器
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(200);
//發出請求
HttpUtils.login(username, password, result, new LoginCallback<String>() {
@Override
public void success(String data) {
Toast.makeText(ScanActivity.this, username + " : " + password + " : " + result, Toast.LENGTH_SHORT).show();
finish();
}
@Override
public void failed(String message, Throwable t) {
Toast.makeText(ScanActivity.this, "發送失敗", Toast.LENGTH_SHORT).show();
finish();
}
});
//zXingView.startSpot();//可以多次識別
}
@Override
public void onScanQRCodeOpenCameraError() {
Toast.makeText(this, "無法識別", Toast.LENGTH_SHORT).show();
}
}
這裏我使用的是retrofit2網絡請求框架。用到以下三個文件:
1. 請求回調LoginCallback.java,用於在工具類中請求成功後,在activity中修改UI
package com.chris.base.scanloginapp;
/**
* create by Chris Chan
* create on 2019/10/2 16:46
* use for :
*/
public interface LoginCallback<T> {
void success(T data);
void failed(String message, Throwable t);
}
2. retrofit2的請求接口LoginService.java
package com.chris.base.scanloginapp;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;
/**
* create by Chris Chan
* create on 2019/10/2 16:45
* use for :
*/
public interface LoginService {
//登錄
@GET("api/user/login")
Call<Boolean> login(@QueryMap Map<String, Object> map);
}
3. 封裝好進行http請求的工具類HttpUtils.java
package com.chris.base.scanloginapp;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* create by Chris Chan
* create on 2019/10/2 16:44
* use for :
*/
public class HttpUtils {
public static void login(String username, String password, String code, final LoginCallback<String> callback) {
String baseUrl = "http://192.168.0.100:7021/";
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(10L, TimeUnit.SECONDS)
.writeTimeout(10L, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl)
.build();
LoginService loginService = retrofit.create(LoginService.class);
Map<String, Object> paramMap = new HashMap<>(16);
paramMap.put("username", username);
paramMap.put("password", password);
paramMap.put("code", code);
Call<Boolean> call = loginService.login(paramMap);
call.enqueue(new Callback<Boolean>() {
@Override
public void onResponse(Call<Boolean> call, Response<Boolean> response) {
if (response.body()) {
callback.success("登陸成功");
}
}
@Override
public void onFailure(Call<Boolean> call, Throwable t) {
callback.failed(t.getMessage(), t);
}
});
}
}
八、需要再次說明的是,這裏的登錄分兩種,一種是App自己的登陸,會獲取到用戶信息。在這裏我們沒有做這個實現,只需要輸入用戶名和密碼就視同登陸了。不過用戶名和密碼必須要是服務端在內存中添加過的那四個,否則後面驗證不會通過。第二種登錄時幫助web端進行的掃碼登錄,會攜帶自己的重要識別信息和掃描的識別碼去登錄,不爲自己,是爲了web端。掃碼之前必須app是登錄的。
經測試,三個端之間邏輯通暢。