Android market launch and summary of difficult problems

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)and onPermissionRequestCanceled(PermissionRequest) . Must be called from the UI thread grant()or deny()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_STORAGEOnly 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 update

Android 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.

resize,m_fill,w_320,h_180

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:

559e9ff87f7f4a01a3ebfe39aa7c3180.png

 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

1da58c8f4fab42339648a8b77bd400db.png

After the correction, the newline can be normal, and it will not be broken according to the word data

9c286376908b4515b7b2767c1c3154f5.png

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

6b52c9928dc14b2c8f40c24154d0e60c.png

 Let's take glide as an example, first add a

87dc97ab526e47729b7ae5ace9d48408.png

 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

b9bbc06f3ed24b948a86ee49a5983b3c.png

 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 (*)

986340ca46174ab1be086d0f84beb469.png

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

a71233a224db4555bca6c68d5ba2de96.png

4cbd69c03ad5473e809b504343f9637e.png

 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

9e8fefcb52a24a268b4192da968127fb.png

 The solution is to locate the location of the resource and find out which library conflicts

8c32ed397a8646e4a4346daada75cf48.png

 c1716c6eaf7d4a16ab6d08d42e61666a.png

 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

d07b9f6142a04c36b35106e1522d4999.png

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

Guess you like

Origin blog.csdn.net/qq_29848853/article/details/129517212