First, when Webveiw loads face authentication, it will prompt a crash without android.webkit.resource.VIDEO_CAPTURE permission. Later, I checked the relevant api. This permission belongs to a web resource access permission in android.webkit.PermissionRequest. The official definition is as follows:
This class defines a permission request and is used when web content requests access to protected resources. Permission request related events are passed
onPermissionRequest(PermissionRequest)
andonPermissionRequestCanceled(PermissionRequest)
. Must be called from the UI threadgrant()
ordeny()
to respond to the request. Protected resources with new names not defined here may be requested in future versions of WebView, even when running on older Android versions. To avoid inadvertently granting requests for new permissions, you should pass the specific permissions you want to grant to thegrant()
It means that we are using the webview to activate the mobile phone hardware functions (camera, audio and video recording, etc.), and we need to grant relevant permissions before we can activate the resources in the webview. Sample code:
new WebChromeClient() {
@Override
public void onPermissionRequest(PermissionRequest request) {
//super.onPermissionRequest(request);
runOnUiThread(new Runnable() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
request.grant(request.getResources());
}
});
}
};
Notice:
super.onPermissionRequest(request); to comment out
runOnUiThread to grant permissions in the UI thread
Second, each version of android mainly changes and adapts, and each update of android continuously reduces the user's authority to control the machine, thereby improving the user experience of the product. Each application market is also updating the corresponding rules to restrict the release of applications that do not comply with the rules, so as to maintain the ecological and pure market.
2.1 Below andorid6, the user rights are relatively large, so the development boards are basically produced based on android5
2.2 Starting with android6 (API23), permission control is started. Sensitive permissions need to be dynamically applied (camera, storage and other permissions), and ordinary permissions can only be configured in the manifest file (network and other permissions)
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 100;
//读写文件权限
private static String[] PERMISSIONS_STORAGE = {"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//android6以上需要动态申请
if (Build.VERSION.SDK_INT >= 23) {
checkPermission();
}
}
private void checkPermission() {
//权限是否授权存储权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//申请权限
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_CODE);
} else {
//已授权存储权限,做其它事情
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
//权限的申请结果
case REQUEST_CODE: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//授权成功
Toast.makeText(this, "存储权限授权成功!", Toast.LENGTH_SHORT).show();
} else {
//授权失败
Toast.makeText(this, "存储权限授权失败!", Toast.LENGTH_SHORT).show();
}
}
}
}
}
2.3 android7(API24)
The shared file limit between applications needs to be adapted to FileProvider, otherwise the application will crash, and file:// URI will trigger FileUriExposedException
The first step is to add a provider node in the AndroidManifest.xml of the app module
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapplication.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<!--exported 要为 false,否则会报安全异常,grantUriPermissions 为 true,表示授予 URI 临时访问权限-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
The meaning of the provider field is as follows:
1. android:authorities identifies the uniqueness of the ContentProvider, which can be defined arbitrarily, preferably globally unique.
2. android:name refers to the previously defined FileProvider subclass.
3. android:exported="false" restricts other applications from obtaining Provider.
4. android:grantUriPermissions="true" grants other applications permission to access Uri.
5. meta-data includes the alias application table.
5.1. The value of android:name is fixed, indicating that the file_path needs to be parsed.
5.2, android:resource defines the mapping table implemented by itself
The second step is to create a new xml folder under the res folder of the app module. The name is the content corresponding to the above android:resource="@xml/file_paths"
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="picture"
path="" />
</paths>
2.4 android8(API26)
All notifications need to provide notification channels Notification Channels, otherwise, all notifications cannot be displayed normally on the 8.0 system
public void showNotification(Context context, String tittle, String content, Bitmap bitmap, String extras) {
Intent intent = new Intent(context, OpenClickActivity.class);
intent.putExtra("customerMessage", extras);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, 0);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
Notification.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
// create android channel
NotificationChannel androidChannel = new NotificationChannel(ANDROID_CHANNEL_ID,
ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(androidChannel);
builder = new Notification.Builder(context, ANDROID_CHANNEL_ID);
} else {
builder = new Notification.Builder(context);
}
Notification notification = builder
/**设置通知左边的图标**/
.setSmallIcon(R.mipmap.juailog)
/**设置通知右边的小图标**/
.setLargeIcon(bitmap)
/**通知首次出现在通知栏,带上升动画效果的**/
.setTicker("通知来了")
/**设置通知的标题**/
.setContentTitle(tittle)
/**设置通知的内容**/
.setContentText(content)
/**通知产生的时间,会在通知信息里显示**/
.setWhen(System.currentTimeMillis())
/**设置该通知优先级**/
.setPriority(Notification.PRIORITY_DEFAULT)
/**设置这个标志当用户单击面板就可以让通知将自动取消**/
.setAutoCancel(true)
/**设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)**/
.setOngoing(false)
/**向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置,使用defaults属性,可以组合:**/
.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND)
.setContentIntent(pendingIntent)
.build();
/**发起通知**/
int id = (int) (System.currentTimeMillis() / 1000);
notificationManager.notify(id, notification);
}
The background application is not allowed to start the background service, you need to use startForegroundService to specify it as the foreground service, otherwise the system will stop the Service and throw an exception
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Intent intent =new Intent(this, MyService.class)
//判断android版本大于8
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
2.5 android9(API28)
HTTP network requests are limited, and requests cannot be sent normally. If we need to use HTTP requests, there are two methods
Method 1: You need to configure HTTP plaintext support in the manifest file
<application
android:usesCleartextTraffic="true">
</application>
Method 2: Alternatively, you can specify a domain name and create a new file network_config.xml in the xml directory of res
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<!--trust system while release only-->
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
<application
android:networkSecurityConfig="@xml/network_config">
</application>
2.6 Android10(API29)
Changes in user storage permissions, partition storage, android provides an "isolated storage sandbox" for each application in the external storage device, and other applications cannot directly access your application's sandbox files.
Internal application private directory: no need to apply for permission, only accessible within this application
context.getCacheDir(); /data/data/package name/cache
context.getFilesDir(); /data/data/包名/files
External storage directory: Not only storage permissions are required, but all file management permissions are also required
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
Environment.getExternalStorageDirectory() /storage/emulated/0
External public directory: only storage permission required
/storage/emulated/0/DCIM, and many other standard paths such as MOVIE/MUSIC Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); /storage/emulated/0/DCIM
Where to access files | required permissions | access method |
---|---|---|
App's private directory | Access without permission | getExternalFilesDir() |
Private directories of other applications | None, but the target file should be marked as shareable by its application using FileProvider | via ParcelFileDescriptor |
Media file directory (audio, photo, video files) | READ_EXTERNAL_STORAGE Only required to access files from other apps) |
MediaStore API |
download catalog | Access without permission | Storage Access Framework SAF |
2.7 Android11 (API30) mandatory partition storage
Android10 (api29) can use the following code to mark the use of the old storage method
<application android:requestLegacyExternalStorage="true" </application>
Android11 (api30) is mandatory to use Scoped Storage (partitioned storage) storage method.
2.8 Android12(API31)
Restrictions on starting an Activity in the background. When the application is in the background, the Activity cannot be started. For example, on the startup page, the countdown of the advertising page may start the activity from the background. In this case, the google market may reject the application.
Google's suggestion is to provide information to users by creating notifications while in the background. The user starts the Activity by clicking on the notification instead of directly.
If necessary, you can also use setFullScreenIntent() to emphasize that this is a notification that needs to be processed immediately.
You can refer to the notification adaptation in Android8 to handle the notification of starting the activity in the background
2.9 Overview of major update adaptation from 6.0 to 13:
Android 6: Dynamic application of permissions at runtime
Android 7: Forbidden to disclose file:// URIs outside your application. To share files, use the FileProvider class.
Android 8: Introduced a notification channel, which does not allow background applications to start background services. You need to pass startForegroundService() is designated as a foreground service, and the application has five seconds to call the startForeground() method of the Service to display a visible notification.
Android 9: In the network request, it is required to use https, Liu Haiping API to support
Android 10: Location permissions, partition storage, restrictions on starting activities in the background, dark theme
Android 11: Storage mechanism update, permission update, permission update, package visibility , foreground service type, message box updateAndroid 12: New application startup page, statement exported, precise alarm clock SCHEDULE_EXACT_ALARM permission: precise location permission, prohibit starting foreground services from the background: location permission is no longer required for searching Bluetooth
Android 13: Subdivided media permissions, WebView discarded setAppCacheEnabled and setForceDark methods, registered visibility to be set for static broadcasts, added notification permissions POST_NOTIFICATIONS, added Wi-Fi runtime permissions NEARBY_WIFI_DEVICES, added body sensor background permissions BODY_SENSORS_BACKGROUND, cut Board content hiding API, non-SDK interface limitation
3. Reasons for Google Market Rejection
3.1 From 2021 onwards, Google and the domestic application market will strictly control the collection of user information without informing users. If functions involving user privacy must be clearly informed of the purpose of collecting this information, users can choose whether to use it or not. Agree that the app collects relevant information, including but not limited to location, Mac address, device id and other personal information.
Solution:
Step 1: When the user installs and opens the app for the first time, the user must first show the user what permissions and information will be obtained in the form of a pop-up box. The user must agree to the information before entering the app homepage and using its functions. At the same time, there must be a place to check the user agreement and privacy policy twice in the application settings.
Step 2: When the user logs in, the user must first check and check the registration agreement and privacy agreement. If the user checks, it means that the user agrees to these agreements.
Step 3: When it comes to recommendations and interest in these sensitive advertising content, users must be able to turn off these recommendations. Usually, adding a channel in the settings can set the display and closure of these contents, and these contents are not recommended.
3.2 Network security and webview vulnerabilities are rejected. From 2021, Google prohibits the interface protocol from using http, and can only use the security protocol https
Solution:
Step 1: Need to add https support
Part 2: Handle the error program of WebView SSL, check the validity of the certificate in a reminded way, example:
class myWebclient extends WebViewClient {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
String message = "SSL Certificate error.";
switch (error.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted.";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired.";
break;
case SslError.SSL_IDMISMATCH:
message = "The certificate Hostname mismatch.";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid.";
break;
case SslError.SSL_DATE_INVALID:
message = "The date of the certificate is invalid";
break;
case SslError.SSL_INVALID:
default:
message = "A generic error occurred";
break;
}
message += " Do you want to continue anyway?";
builder.setTitle("SSL Certificate Error");
builder.setMessage(message);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
}
3.3 Starting in 2022, Google will prohibit applications that start activit from the background, and such applications have been removed from the shelves as of 2023.
Solution:
The second part mentions the background start activity adaptation method of android10 and android11, which reminds the user to start the activity in the background by way of notification. Or prohibit activity jumping in the background state, do interception processing, and wait for the application to return to the foreground before jumping again;
Example of judging the front and back status:
public class AppFrontBackHelper {
private OnAppStatusListener mOnAppStatusListener;
public AppFrontBackHelper() {
}
/**
* 注册状态监听,仅在Application中使用
*
* @param application
* @param listener
*/
public void register(Application application, OnAppStatusListener listener) {
mOnAppStatusListener = listener;
application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
}
public void unRegister(Application application) {
application.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
}
private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
//打开的Activity数量统计
private int activityStartCount = 0;
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
activityStartCount++;
//数值从0变到1说明是从后台切到前台
if (activityStartCount == 1) {
//从后台切到前台
if (mOnAppStatusListener != null) {
mOnAppStatusListener.onFront();
}
}
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
activityStartCount--;
//数值从1到0说明是从前台切到后台
if (activityStartCount == 0) {
//从前台切到后台
if (mOnAppStatusListener != null) {
mOnAppStatusListener.onBack();
}
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
};
public interface OnAppStatusListener {
void onFront();
void onBack();
}
}
use:
//监听前后端状态
AppFrontBackHelper helper = new AppFrontBackHelper();
helper.register(this, new AppFrontBackHelper.OnAppStatusListener() {
@Override
public void onFront() {
//应用切到前台处理
Log.e("aaa", "前台");
Consts.ISBACKPROCESS=false;
}
@Override
public void onBack() {
//应用切到后台处理
Log.e("aaa", "后台");
Consts.ISBACKPROCESS = true;
}
});
3.4 Newly launched applications after August 2021 must be packaged in aab format, and uploading of apk packages is no longer supported.
But there is another problem. When aab is put on the shelves, it always prompts that the app bundle signature is invalid.
Solution:
1. It is very important that the project build.gradle before packaging must not configure the signature, otherwise Google will not recognize your signature
2. Just select the aab packaging method for packaging
3. Select the google signature plan on the shelf and let Google manage the signature
4. Then you can pass the aab package and complete the relevant information
3.5 android.permission.REQUEST_INSTALL_PACKAGES allows the installation of third-party software permission caused by the rejection
Since 2021, Google has banned the installation of apps from other methods, and can only update apps from the Google Market, so the prohibition list file prohibits the configuration of EQUEST_INSTALL_PACKAGES permissions
Rejected reasons for google response:
This can only remove this permission. If you strongly require internal updates, you can use other solutions. After all, this permission is only used to limit automatic installation.
Solution 1: Click Update to directly jump to the built-in browser of the mobile phone, download and install through a third-party browser, after all, Google can only restrict its own applications, and cannot restrict other applications.
Solution 2: Since it cannot be installed automatically, install it manually, or download the upgrade package in the app to the memory folder, and then manually find the corresponding upgrade package and click Install.
In this way, in order to ensure that the user can update, the two solutions can be combined. If there is a browser, the browser will download the update. Otherwise, it will be manually updated after downloading to the memory.
//跳系统浏览器更新
private void launchAppBrowser(String url) {
try {
Intent intent= new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction("android.intent.action.VIEW");
intent.setData(Uri.parse(url));
activity.startActivity(intent);
dismiss();
}catch (Exception e){
//浏览器不存在
downFaceFiles(url);
}
}
//下载升级包到公共下载目录
private void downFaceFiles(String downPath) {
String folder =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getAbsolutePath()+ File.separator + "lensun";
String mSinglePath = folder + File.separator + System.currentTimeMillis() + suffix;
FileDownloader.getImpl().create(downPath).setPath(mSinglePath, false)
}
Of course, if Google has already put it on the shelves, you can jump to the Google market to update the app. The above plan is that if the app is taken off the shelf by Google, there are other channels to update it.
//跳转Google市场
public void launchAppGoogle() {
Intent intent2 = new Intent(Intent.ACTION_VIEW);
intent2.setData(Uri.parse("https://play.google.com/store/apps/details?id=" +
activity.getPackageName()));
activity.startActivity(intent2);
}
Four, TextView word wrapping problem
There is a problem, there is a lot of space on the right side of a line, but when long numbers or words are encountered, the line will automatically wrap, causing the interface to be very untidy
After the correction, the newline can be normal, and it will not be broken according to the word data
Method: It is said on the Internet that the android:breakStrategy="simple" attribute can be set in xml, but the problem cannot be solved after practice, so write a control yourself to solve it
public class AlignTextView extends android.support.v7.widget.AppCompatTextView {
public AlignTextView(Context context) {
super(context);
}
public AlignTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
// 获取用于显示当前文本的布局
Layout layout = getLayout();
if (layout == null) return;
final int lineCount = layout.getLineCount();
if (lineCount < 2) {
//想只有一行 则不需要转化
super.onDraw(canvas);
return;
}
Paint.FontMetrics fm = getPaint().getFontMetrics();
int textHeight = (int) (Math.ceil(fm.descent - fm.ascent));
textHeight = (int) (textHeight * layout.getSpacingMultiplier() + layout.getSpacingAdd());
measureText(getMeasuredWidth(), getText(), textHeight, canvas);
}
/**
* 计算一行 显示的文字
*
* @param width 文本的宽度
* @param text//文本内容
* @param textHeight 文本大小
*/
public void measureText(int width, CharSequence text, int textHeight, Canvas canvas) {
TextPaint paint = getPaint();
paint.setColor(getCurrentTextColor());
paint.drawableState = getDrawableState();
float textWidth = StaticLayout.getDesiredWidth(text, paint);
int textLength = text.length();
float textSize = paint.getTextSize();
if (textWidth < width) canvas.drawText(text, 0, textLength, 0, textSize, paint); //不需要换行
else {
//需要换行
CharSequence lineOne = getOneLine(width, text, paint);
int lineOneNum = lineOne.length();
canvas.drawText(lineOne, 0, lineOneNum, 0, textSize, paint);
//画第二行
if (lineOneNum < textLength) {
CharSequence lineTwo = text.subSequence(lineOneNum, textLength);
lineTwo = getTwoLine(width, lineTwo, paint);
canvas.drawText(lineTwo, 0, lineTwo.length(), 0, textSize + textHeight, paint);
}
}
}
public CharSequence getTwoLine(int width, CharSequence lineTwo, TextPaint paint) {
int length = lineTwo.length();
String ellipsis = "...";
float ellipsisWidth = StaticLayout.getDesiredWidth(ellipsis, paint);
for (int i = 0; i < length; i++) {
CharSequence cha = lineTwo.subSequence(0, i);
float textWidth = StaticLayout.getDesiredWidth(cha, paint);
if (textWidth + ellipsisWidth > width) {//需要显示 ...
lineTwo = lineTwo.subSequence(0, i - 1) + ellipsis;
return lineTwo;
}
}
return lineTwo;
}
/**
* 获取第一行 显示的文本
*
* @param width 控件宽度
* @param text 文本
* @param paint 画笔
* @return
*/
public CharSequence getOneLine(int width, CharSequence text, TextPaint paint) {
CharSequence lineOne = null;
int length = text.length();
for (int i = 0; i < length; i++) {
lineOne = text.subSequence(0, i);
float textWidth = StaticLayout.getDesiredWidth(lineOne, paint);
if (textWidth >= width) {
CharSequence lastWorld = text.subSequence(i - 1, i);//最后一个字符
float lastWidth = StaticLayout.getDesiredWidth(lastWorld, paint);//最后一个字符的宽度
if (textWidth - width < lastWidth) {//不够显示一个字符 //需要缩放
lineOne = text.subSequence(0, i - 1);
}
return lineOne;
}
}
return lineOne;
}
}
Five, jar package dependency conflict
5.1 Use the gradle command to analyze dependencies
View all dependencies
./gradlew [module]:dependencies
For example ./gradlew app:dependencies
View only the dependency tree of the implementation
./gradlew [module]:dependencies --configuration [variants 类型]
比如:./gradlew :app:dependencies --configuration implementation
View the dependencies of the specified library
./gradlew :app:dependencyInsight --dependency [specify] --configuration compile
比如:./gradlew :app:dependencyInsight --dependency fastjson --configuration compile
5.2 Task tool analysis
gradle -> app -> tasks -> help -> dependencies
Let's take glide as an example, first add a
You can see that there is only one glide dependency
+--- project :multidexapplication (*)
\--- com.github.bumptech.glide:glide:4.11.0
+--- com.github.bumptech.glide:gifdecoder:4.11.0
| \--- androidx.annotation:annotation:1.0.0
+--- com.github.bumptech.glide:disklrucache:4.11.0
+--- com.github.bumptech.glide:annotations:4.11.0
+--- androidx.fragment:fragment:1.0.0
| +--- androidx.core:core:1.0.0
| | +--- androidx.annotation:annotation:1.0.0
| | +--- androidx.collection:collection:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0
| | +--- androidx.lifecycle:lifecycle-runtime:2.0.0
| | | +--- androidx.lifecycle:lifecycle-common:2.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.arch.core:core-common:2.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0
| | \--- androidx.versionedparcelable:versionedparcelable:1.0.0
| | +--- androidx.annotation:annotation:1.0.0
| | \--- androidx.collection:collection:1.0.0 (*)
| +--- androidx.legacy:legacy-support-core-ui:1.0.0
| | +--- androidx.annotation:annotation:1.0.0
| | +--- androidx.core:core:1.0.0 (*)
| | +--- androidx.legacy:legacy-support-core-utils:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.core:core:1.0.0 (*)
| | | +--- androidx.documentfile:documentfile:1.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.loader:loader:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.core:core:1.0.0 (*)
| | | | +--- androidx.lifecycle:lifecycle-livedata:2.0.0
| | | | | +--- androidx.arch.core:core-runtime:2.0.0
| | | | | | +--- androidx.annotation:annotation:1.0.0
| | | | | | \--- androidx.arch.core:core-common:2.0.0 (*)
| | | | | +--- androidx.lifecycle:lifecycle-livedata-core:2.0.0
| | | | | | +--- androidx.lifecycle:lifecycle-common:2.0.0 (*)
| | | | | | +--- androidx.arch.core:core-common:2.0.0 (*)
| | | | | | \--- androidx.arch.core:core-runtime:2.0.0 (*)
| | | | | \--- androidx.arch.core:core-common:2.0.0 (*)
| | | | \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | \--- androidx.print:print:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0
| | +--- androidx.customview:customview:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | \--- androidx.core:core:1.0.0 (*)
| | +--- androidx.viewpager:viewpager:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.core:core:1.0.0 (*)
| | | \--- androidx.customview:customview:1.0.0 (*)
| | +--- androidx.coordinatorlayout:coordinatorlayout:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.core:core:1.0.0 (*)
| | | \--- androidx.customview:customview:1.0.0 (*)
| | +--- androidx.drawerlayout:drawerlayout:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.core:core:1.0.0 (*)
| | | \--- androidx.customview:customview:1.0.0 (*)
| | +--- androidx.slidingpanelayout:slidingpanelayout:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.core:core:1.0.0 (*)
| | | \--- androidx.customview:customview:1.0.0 (*)
| | +--- androidx.interpolator:interpolator:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0
| | +--- androidx.swiperefreshlayout:swiperefreshlayout:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.core:core:1.0.0 (*)
| | | \--- androidx.interpolator:interpolator:1.0.0 (*)
| | +--- androidx.asynclayoutinflater:asynclayoutinflater:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | \--- androidx.core:core:1.0.0 (*)
| | \--- androidx.cursoradapter:cursoradapter:1.0.0
| | \--- androidx.annotation:annotation:1.0.0
| +--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
| +--- androidx.annotation:annotation:1.0.0
| +--- androidx.loader:loader:1.0.0 (*)
| \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0 (*)
+--- androidx.vectordrawable:vectordrawable-animated:1.0.0
| +--- androidx.vectordrawable:vectordrawable:1.0.0
| | +--- androidx.annotation:annotation:1.0.0
| | \--- androidx.core:core:1.0.0 (*)
| \--- androidx.legacy:legacy-support-core-ui:1.0.0 (*)
\--- androidx.exifinterface:exifinterface:1.0.0
\--- androidx.annotation:annotation:1.0.0
Let's add a different version of glide
You can see that there is an extra glide dependency conflict at the end
+--- project :multidexapplication (*)
+--- com.github.bumptech.glide:glide:4.11.0
| +--- com.github.bumptech.glide:gifdecoder:4.11.0
| | \--- androidx.annotation:annotation:1.0.0
| +--- com.github.bumptech.glide:disklrucache:4.11.0
| +--- com.github.bumptech.glide:annotations:4.11.0
| +--- androidx.fragment:fragment:1.0.0
| | +--- androidx.core:core:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.collection:collection:1.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.lifecycle:lifecycle-runtime:2.0.0
| | | | +--- androidx.lifecycle:lifecycle-common:2.0.0
| | | | | \--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.arch.core:core-common:2.0.0
| | | | | \--- androidx.annotation:annotation:1.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | \--- androidx.versionedparcelable:versionedparcelable:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | \--- androidx.collection:collection:1.0.0 (*)
| | +--- androidx.legacy:legacy-support-core-ui:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.core:core:1.0.0 (*)
| | | +--- androidx.legacy:legacy-support-core-utils:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.core:core:1.0.0 (*)
| | | | +--- androidx.documentfile:documentfile:1.0.0
| | | | | \--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.loader:loader:1.0.0
| | | | | +--- androidx.annotation:annotation:1.0.0
| | | | | +--- androidx.core:core:1.0.0 (*)
| | | | | +--- androidx.lifecycle:lifecycle-livedata:2.0.0
| | | | | | +--- androidx.arch.core:core-runtime:2.0.0
| | | | | | | +--- androidx.annotation:annotation:1.0.0
| | | | | | | \--- androidx.arch.core:core-common:2.0.0 (*)
| | | | | | +--- androidx.lifecycle:lifecycle-livedata-core:2.0.0
| | | | | | | +--- androidx.lifecycle:lifecycle-common:2.0.0 (*)
| | | | | | | +--- androidx.arch.core:core-common:2.0.0 (*)
| | | | | | | \--- androidx.arch.core:core-runtime:2.0.0 (*)
| | | | | | \--- androidx.arch.core:core-common:2.0.0 (*)
| | | | | \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0
| | | | | \--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
| | | | | \--- androidx.annotation:annotation:1.0.0
| | | | \--- androidx.print:print:1.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.customview:customview:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | \--- androidx.core:core:1.0.0 (*)
| | | +--- androidx.viewpager:viewpager:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.core:core:1.0.0 (*)
| | | | \--- androidx.customview:customview:1.0.0 (*)
| | | +--- androidx.coordinatorlayout:coordinatorlayout:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.core:core:1.0.0 (*)
| | | | \--- androidx.customview:customview:1.0.0 (*)
| | | +--- androidx.drawerlayout:drawerlayout:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.core:core:1.0.0 (*)
| | | | \--- androidx.customview:customview:1.0.0 (*)
| | | +--- androidx.slidingpanelayout:slidingpanelayout:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.core:core:1.0.0 (*)
| | | | \--- androidx.customview:customview:1.0.0 (*)
| | | +--- androidx.interpolator:interpolator:1.0.0
| | | | \--- androidx.annotation:annotation:1.0.0
| | | +--- androidx.swiperefreshlayout:swiperefreshlayout:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | +--- androidx.core:core:1.0.0 (*)
| | | | \--- androidx.interpolator:interpolator:1.0.0 (*)
| | | +--- androidx.asynclayoutinflater:asynclayoutinflater:1.0.0
| | | | +--- androidx.annotation:annotation:1.0.0
| | | | \--- androidx.core:core:1.0.0 (*)
| | | \--- androidx.cursoradapter:cursoradapter:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0
| | +--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
| | +--- androidx.annotation:annotation:1.0.0
| | +--- androidx.loader:loader:1.0.0 (*)
| | \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0 (*)
| +--- androidx.vectordrawable:vectordrawable-animated:1.0.0
| | +--- androidx.vectordrawable:vectordrawable:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0
| | | \--- androidx.core:core:1.0.0 (*)
| | \--- androidx.legacy:legacy-support-core-ui:1.0.0 (*)
| \--- androidx.exifinterface:exifinterface:1.0.0
| \--- androidx.annotation:annotation:1.0.0
\--- com.github.bumptech.glide:glide:4.8.0 -> 4.11.0 (*)
Notice:
1. A dependency with (*) indicates that the library has multiple versions and has been overwritten by a higher version
2. 4.8.0 -> 4.11.0 (*) indicates that the higher version will be used first. If you need to use a lower version of the API, you need to exclude the higher version
5.3 Method to resolve dependency conflicts
Exclude group or groups
Exclude com.android.support group from com.github.bumptech.glide:glide:4.11.0 dependency, including supportv7, supportv4
api("com.github.bumptech.glide:glide:4.11.0") { exclude group : 'com.android.support' }Exclude the supportV4 module
api("com.github.bumptech.glide:glide:4.11.0") { exclude module: 'support-v4' } from the dependency library com.github.bumptech.glide:glide:4.11.0
Specify the version separately
configurations.all { // traverse all dependent libraries resolutionStrategy.eachDependency { DependencyResolveDetails details -> def requested = details.requested // find all support libraries if (requested.group == 'com.android.support:support-v4') { // multidex uses 26.0.0, others use version 28.0.0 if (requested.name.startsWith("glide")) { details.useVersion '26.0.0' }else { details.useVersion '28.0.0' } } } }
Unified specified version, the support-v4 package is forced to use version 28.0.0
configurations.all {
resolutionStrategy {
force 'com.android.support:support-v4:28.0.0'
}
}
5.4 The integrated Rongyun SDK version of the instance does not match. Since the gradle compilation environment that the new version of the library depends on is high, and the library version inside is also high, it is necessary to exclude the high version of the library or lower the version at this time
error message
Solution: exclude the library with error message
implementation ('cn.rongcloud.sdk:im_kit:5.4.0')
{
exclude group: 'androidx.lifecycle', module: 'lifecycle-runtime'
exclude group: 'androidx.room', module: 'room-runtime'
}
5.5 Resource conflicts, aapt basically has different versions of libraries
The solution is to locate the location of the resource and find out which library conflicts
You can see that there are multiple versions referenced by appcompat. At this time, you can unify the versions and change them all to version 1.2.0. You can see that there is no such error when compiling again.
Six, retain two decimal places
6.1 Usage Scenarios
Those that need to keep decimals are generally related to the amount. Most of the money-related applications are mall applications. This type of application has very strict requirements on the amount. After all, neither the customer nor the platform wants to suffer. If it is true, even a penny error may cause dissatisfaction. So the decimal place must be handled properly
6.2 Examples
After our project has been running for a period of time, there is a refund problem. I bought multiple items but can refund after sales in batches. At this time, it came out that the amount calculated by the app was different from the actual amount. The actual payment amount is 10 yuan, but because the app calculates the unit price is 3.33, the quantity is 3, resulting in a maximum refund of 9.99 in the end
6.3 The solution, the final solution given by the product is to make up the price difference to the amount of the last refund, to ensure that the actual payment and the refund amount are consistent
6.4 The method of calculating the amount based on the above has to be consistent with the backend, whether to round up or only intercept two digits
The conventional method of retaining two digits is rounding, as follows:
The first, rounded
BigDecimal a = new BigDecimal(123456789.03);
DecimalFormat df = new DecimalFormat("##0");
System.out.println(df.format(a)));
The second, rounded
BigDecimal bg = new BigDecimal(123456789.03);
double f1 = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
System.out.println(f1);
The third type is not rounded, which is strange in use, and most of them are fine, but when encountering the second decimal place of 3, it will become 2, such as 123456789.03
DecimalFormat formater = new DecimalFormat();
formater.setMaximumFractionDigits(2);
formater.setGroupingSize(0);
formater.setRoundingMode(RoundingMode.FLOOR);
System.out.println(formater.format(123456789.03));
The fourth type, do not round off, directly intercept
BigDecimal a = new BigDecimal(123456789.03).divide(new BigDecimal(1000)).setScale(2,BigDecimal.ROUND_DOWN);
System.out.println(a);
Choose which one can be used according to your actual situation
6.2 Accurate tools must be used for calculations involving addition, subtraction, multiplication and division, otherwise normal calculations may result in more or less 1 cent due to precision problems
For example: normal operation 2.0-1.1=0.8999999
This leads to the loss of precision. If you do not allow rounding to keep two digits in the end, it will become 0.89, which is definitely wrong.
So use the precise calculation tool BigDecimal
This calculation is: normal operation 2.0-1.1=0.9
/**
* 加减法精确计算类
*/
public static Double add(Double parms1, Double param2) {
if (parms1 == null) {
parms1 = 0D;
}
f (param2 == null) {
param2 = 0D;
}
return new BigDecimal(String.valueOf(parms1)).add(new BigDecimal(String.valueOf(param2))).doubleValue();
}
/**
* 减法
*/
public static Double subtract(Double parms1, Double param2) {
if (parms1 == null) {
parms1 = 0D;
}
if (param2 == null) {
param2 = 0D;
}
return new BigDecimal(String.valueOf(parms1)).subtract(new BigDecimal(String.valueOf(param2))).doubleValue();
}
/**
* 乘法
*/
public static Double multiply(Double parms1, Double param2) {
if (parms1 == null) {
parms1 = 0D;
}
if (param2 == null) {
param2 = 0D;
}
return new BigDecimal(String.valueOf(parms1)).multiply(new BigDecimal(String.valueOf(param2))).doubleValue();
}
/**
* 除法
* digit:小数位数
* roundType:四舍五入或者向下取整
*/
public static Double divide(Double parms1, Double param2, int digit, int roundType) {
if (parms1 == null) {
parms1 = 0D;
}
if (param2 == null || param2 == 0) {
return 0D;
}
return new BigDecimal(String.valueOf(parms1)).divide(new BigDecimal(String.valueOf(param2)), digit, roundType).doubleValue();
}
Seven, GitHub, Gitee download and upload failed
It may be that the file is too large, or the SSL protocol verification is incorrect, or
Try executing the following commands in turn
Remove the verification, or change the verification version
git config http.sslVerify "false"
git config http.sslVersion tlsv1.2
Increase the git buffer
git config --global http.postBuffer 1048576000