This article is mainly to collect some of their own encountered some problems, to facilitate their summary, but also to facilitate others to solve the problem! At present less, bit by bit collection, the road is a step by step, pit also want to step on one by one.

  • A memory leak
  • Adapter refresh
  • SharedPreferences data cannot be saved
  • GlideApp cannot be generated automatically
  • Failed to generate a custom View
  • AndroidStudio upgrade to 3.6 exception
  • More than one file was found with OS independent path
  • MediaPlayer Error
  • The server is abnormal after user-defined message encryption. Procedure
  • The horizontal screen size is suddenly enlarged
  • EditText limits input to uppercase letters and numbers
  • OnBackPressedDispatcher event
  • The ViewPager2#Adapter crashes
  • Bundletool crashes after parsing aAB

1. Memory leaks

An old project I was in charge of recently ran into a very strange bug. The test described an Activity that would crash when clicked on a button the first time it started. Check log is View null pointer exception. My first reaction was memory, so I started checking in this direction. No exception is found through the Profiler check, and no exception is found through LeakCanary. Finally, the method is called to analyze. This method can be executed an infinite number of times the first time the Activity is entered, the second time it is entered the first time is fine, but the second time it crashes. When I later examined the method, I found that the second time the method was executed, it was called by the PopupWindow callback. PopupWindow is a simple private member variable, not a static member variable. PopupWindow is instantiated using a singleton method, which causes the second interface callback to refer to the Activity that was already recycled. This causes the Activity using the PopupWindow to crash the second time it comes in to use it!

Modify before:private static RecommendPopup instance;
public static RecommendPopup getInstance(Activity context, IMapListener iMapListener){
    if (instance == null)
        instance = new RecommendPopup(context,iMapListener);
    returninstance; } After modification:public RecommendPopup(Activity context,IMapListener iMapListener) {
        super(context);
        this.mActivity = context;
        this.iMapListener = iMapListener;
    }
Copy the code

Solution: this problem, the first step to determine the initialization and call when the same object pointer ID is consistent, if not to find asynchronous, static, singleton. If it’s consistent then look for asynchrony or some other logic who moved it

2. Refresh the Adapter

A few days ago, I encountered a strange thing, the Adapter refresh did not respond. The specific logic is to add a flag bit to the Adapter. After modifying the flag bit, the page is refreshed to find no change. Adding a breakpoint check does enter the notifyDataSetChanged method, and a later look at the source code reveals that the Adapter did not enter the convert method because its data had not changed. View source code:

  /** * Notify any registered observers that the data set has changed. * * <p>There are two different classes of data change events, item changes and structural * changes. Item changes are when a single item has its data updated but no positional * changes have occurred. Structural changes are when items are inserted, removed or moved * within the data set.</p> * * <p>This event does not specify what about the data set has changed, forcing * any observers to assume that all existing items and structure may no longer be valid. * LayoutManagers will be  forced to fully rebind and relayout all visible views.</p> * * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events * for adapters that report that they have {@link #hasStableIds() stable IDs} when
    * this method is used. This can help for the purposes of animation and visual
    * object persistence but individual item views will still need to be rebound
    * and relaid out.</p>
    *
    * <p>If you are writing an adapter it will always be more efficient to use the more
    * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
    * as a last resort.</p>
    *
    * @see #notifyItemChanged(int)
    * @see #notifyItemInserted(int)
    * @see #notifyItemRemoved(int)
    * @see #notifyItemRangeChanged(int, int)
    * @see #notifyItemRangeInserted(int, int)
    * @see #notifyItemRangeRemoved(int, int)
    */
    public final void notifyDataSetChanged(a) {
    mObservable.notifyChanged();
    }
    
    
    
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {...public void notifyChanged(a) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); }}... }Copy the code

Bind flag bits to data instead of Adapter.

3. SharedPreferences data cannot be saved

Recently, a user of UpdatePlugin, the open source library I am responsible for maintaining, raised a problem: after ignoring the version Settings for the second time, he found that the ignored version number was still the same as the first version number when he entered the application for the second time. After writing the demo test, confirm that this is the SharedPreferences pot. The specific reason is still in the analysis, a little burn brain.

Let’s start with a solution. The user provided a solution:

Modify before:public static void saveIgnoreVersion(int versionCode) {
    Set<String> ignoreVersions = getIgnoreVersions();
    if(! ignoreVersions.contains(String.valueOf(versionCode))) { ignoreVersions.add(String.valueOf(versionCode)); getUpdatePref().edit().putStringSet("ignoreVersions",ignoreVersions).apply(); }}// The user provides the modification method:
public static void saveIgnoreVersion(int versionCode) {
    Set<String> ignoreVersions = getIgnoreVersions();
    if(! ignoreVersions.contains(String.valueOf(versionCode))) { ignoreVersions.add(String.valueOf(versionCode)); getUpdatePref().edit().clear().putStringSet("ignoreVersions",ignoreVersions).apply(); }}// Last modified method:

/** * The framework internally provides some cache data access for use: such as download progress, ignored version. *@author haoge
 */
public class UpdatePreference {

    private static final String PREF_NAME = "update_preference";

    public static List<String> getIgnoreVersions (a) {
        String txt =  getUpdatePref().getString("ignoreVersions"."");
        if(TextUtils.isEmpty(txt))return new ArrayList<>();
        txt = txt.replace("["."").replace("]"."");
        String[] result = txt.split(",");
        / / put an end to the Java. Lang. UnsupportedOperationException
        return new ArrayList<>(Arrays.asList(result));
    }

    public static void saveIgnoreVersion(int versionCode) {
        List<String> ignoreVersions = getIgnoreVersions();
        if(! ignoreVersions.contains(String.valueOf(versionCode))) { ignoreVersions.add(String.valueOf(versionCode)); getUpdatePref().edit().putString("ignoreVersions",ignoreVersions.toString()).apply(); }}private static SharedPreferences getUpdatePref (a) {
        returnActivityManager.get().getApplicationContext().getSharedPreferences(PREF_NAME,Context.MODE_PRIVATE); }}Copy the code

The reason why WE choose to modify the storage mode is that the current bug of putStringSet has not been analyzed. Therefore, this method is excluded for the time being to avoid the occurrence of unknown errors again.

CommitToMemory and writeToFile are saved several times. Why will data be lost when entering the application again after updating the original Set

? ChangesMade is set to the default false to avoid later writing to the file. However, if you change MCR. ChangesMade to prevent data from being written to the file, MMap cannot save the values that need to be saved, so how do you get the correct values after saving (before exiting the application)?

This problem has been troubling me, which big guy has ideas please give directions, thank you?

Document reminder:

Note that you must not modify the set instance returned by this call. The consistency of the stored data is not guaranteed if you do, nor is your ability to modify the instance at all.

Reference article:

PutStringSet data cannot be saved

4. GlideApp cannot be automatically generated

I recently copied a tool class from my colleague’s other project to Kotlin’s new project, and found a problem: GlideApp cannot be generated automatically, so I implemented the following steps:

  1. eachModelUnder thegradleAll needs to configureaptThe plug-in
apply plugin:'kotlin-kapt'
Copy the code
  1. throughGradleRely onGlideAnd make annotations dependent on each useGlideApptheModule
implementation 'com. Making. Bumptech. Glide: glide: 4.10.0'
// Add GlideApp wherever it is used
kapt 'com. Making. Bumptech. Glide: the compiler: 4.10.0'
Copy the code
  1. In each oneModelUnder thegradleAdd the following content to the file:
 android{
      compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }

    kotlinOptions {
        jvmTarget = '1.8'}}Copy the code
  1. And then we synchronizesync now, and then recompileRebuild ProjectCan be

Note that due to gradle environment issues, there is no guarantee that everyone will be able to solve the dependency problem through the above steps. It is only a solution to be verified.

5. Failed to generate a custom View

Today, I was going to customize a View, but when I wrote it, loading it crashed. I checked the code:

class ProgressView (context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attributeSet, defStyleAttr) {
    init{... }... }Copy the code

Exception log:

Caused by: android.view.InflateException: Binary XML file line #15: Binary XML file line #15: Error inflating class com.junerver.videorecorder.ProgressView
     Caused by: android.view.InflateException: Binary XML file line #15: Error inflating class com.junerver.videorecorder.ProgressView
     Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
Copy the code

As soon as I saw the init keyword, I thought there was something wrong with my initialization. After checking the code and finding nothing, I opened Baidu and got this conclusion:

I was a little unconvinced by this, so I took a look at Kotlin’s Java code and, well, I was convinced!

It’s easier to change than to know what’s wrong. It means it’s easy to change when you know what’s wrong! Based on Kotlin’s previous experience with the keyword constructor, I modified the code as follows:

class ProgressView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? , defStyleAttr:Int) : View(context, attributeSet, defStyleAttr) {
    init{... }... }Copy the code

Results error is still indifferent, or the arrogance of the explosion red! Luckily, I know one more move:

class ProgressView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attributeSet, defStyleAttr) {
    init{... }... }Copy the code

The JvmOverloads annotation failed because there was no default value for nullable fields, so there was only one constructor in the absence of a default value. That’s settled at last!

AndroidStudio upgrade to 3.6 exception

Recently, I saw that many people in QQ group have upgraded AndroidStudio to 3.6 official version. I saw that it was quite new, so I took advantage of the beginning of the work is not very busy, so I directly updated it, and then encountered the following problems:

C: \ Users \ \ Administrator \. Gradle, caches, transforms - 2, 126332 c53653ff7049fa8c511808692d files - 2.1 \ \ constraintlayout 2.0.0 - bet a1\res\values\values.xml:255:5-4197: AAPT: error: resource attr/flow_horizontalSeparator (aka com.cn.dcjt.firelibrary:attr/flow_horizontalSeparator) not found.Copy the code

(1) Failed to close aAPT annotation in gradle.properties

android.enableAapt2=false
Copy the code

(2) Try to check the constraintLayout version number of all modules in the project and find that the version number is identical without any difference

(3) Try to check the flow_horizontalSeparator property in the project. No such property is found. Even changing all constraintLayout layouts in the project to relative layouts does not work.

(4) Finally change the version number of constraintLayout from beta version to official version.

/ / modify before
"Androidx. Constraintlayout: constraintlayout: 2.0.0 - beta1"
/ / modified
"Androidx. Constraintlayout: constraintlayout: 1.1.3."
Copy the code

7. Multiple files have paths independent of the operating system

Error log:

More than one file was found with OS independent path 'META-INF/library_release.kotlin_module'
Copy the code

After baidu to see

The compiler recently reported such an error when introducing two AAR libraries written by Kotlin. There are two identical files in the file path meta-INF /library_release.kotlin_module.

After checking, later found to be com. Making. Lygttpod: SuperTextView: with com 2.4.2. Making. ChenBingX: SuperTextView: v3.2.5.99 two sets of framework conflict! Knowing the cause, the solution is simple:

  • Choose between two frames
  • Master of the projectModuleThat is, packaged shell components (generallyapp) added such as writing code:
android {
    packagingOptions {
       exclude 'META-INF/*.kotlin_module'}}Copy the code

Reference:

Blog.csdn.net/yinxing2008…

Deskid. Making. IO / 2019/03/05 /…

8 MediaPlayerError.

Error log:

MediaPlayerNative: error (1, -2147483648)
MediaPlayer: Error (1,-2147483648)
Copy the code

When an Android 10 phone plays an audio file, the host of the audio address is HTTP and there is no domain name. The audio file plays normally on a Huawei phone. However, the preceding exception occurs in the test of xiaomi and OnePlus. Ignoring network complete detection through Baidu still fails.

<application
    android:name=".base.App"
    android:allowBackup="true"
    android:icon="@mipmap/app_logo"
    android:label="@string/app_name"
    android:networkSecurityConfig="@xml/network_config"
    android:usesCleartextTraffic="true"
    android:requestLegacyExternalStorage="true"
    android:roundIcon="@mipmap/app_logo"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
Copy the code

Later looking at the log, system exception for the location, so use ExoPlayer directly. Replaced MediaPlayer.

9. The server is abnormal after user-defined message encryption

Some time ago, I encountered a problem. After the self-defined JSON format string was encrypted, it was sent to the server (the backend of our company was developed for Node JS) and then garbled. Check that the encrypted ciphertext is sent through the POST request. The ciphertext is stored in the RequestBody and the encoding format is not set. After debugging with the server developer, it was found that the server used escape function to encode the ciphertext. At the beginning, I used various methods on the Internet to accommodate this encoding. After testing for many times, I found that it was not correct. After rewriting the decoding/encoding method that was compatible with this method, I found that the escape function had been documented as a non-recommended encoding method, and the recommended encoding method was decodeURI function. Recommended decodeURI function encoding, we use URLDecoder#decode function decoding will not have a problem. If you encounter similar problems in the future can ask the background to use decodeURI function to encode.

See encoding/decoding for details.

10, horizontal screen size suddenly enlarged

I recently had an issue with a page that suddenly doubled in size when it was landscape.

That’s the first thing that happensAutoSizeYes, but it still does when I comment on the screen adaptation parameters:

ViewBinding is a bug that links two layout files to each other. However, it is still not possible to use setContentView to load a page using ViewBinding. The gradle dependency of the framework is annotated. It was not discovered at first because the frame has a default fit size (360*640). However, the density calculated based on the width to height ratio of portrait screen was used globally in landscape screen, and the anomaly described above appeared. Once the problem is identified, we can solve the problem by looking at the documentation and making the Activity implement the empty interface CancelAdapt and simply discontinue the adaptation of the page.

11. EditText limits input to uppercase letters and numbers

At first, when I looked at this requirement, I thought of a custom keyboard. Later, I found that the system soft keyboard could be used to achieve this function. There are three steps as follows:

1 EditText defines the digits attribute implementation as follows:

<EditText
    android:id="@+id/tv_code"
    style="@style/edt_text"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_marginLeft="4dp"
    android:layout_weight="1"
    android:hint="Please enter"
    android:maxLength="17"
    android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"/>

Copy the code

2. Call the EditText setTransformationMethod function

tvCode.transformationMethod = object : ReplacementTransformationMethod() {
    override fun getOriginal(a) = charArrayOf('a'.'b'.'c'.'d'.'e'.'f'.'g'.'h'.'i'.'j'.'k'.'l'.'m'.'n'.'o'.'p'.'q'.'r'.'s'.'t'.'u'.'v'.'w'.'x'.'y'.'z')

    override fun getReplacement(a) = charArrayOf('A'.'B'.'C'.'D'.'E'.'F'.'G'.'H'.'I'.'J'.'K'.'L'.'M'.'N'.'O'.'P'.'Q'.'R'.'S'.'T'.'U'.'V'.'W'.'X'.'Y'.'Z')}Copy the code

Now the page displays uppercase letters and numbers, but tvCode#text still contains lowercase letters, so we need to add a filter to the value as follows:

tvCode.text.toString().toUpperCase()
Copy the code

This allows EditText to enter uppercase letters and numbers.

11,OnBackPressedDispatcherThe event

[Jetpack OnBackPressedDispatcher] [Jetpack OnBackPressedDispatcher

requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,object :
    OnBackPressedCallback(true) {override fun handleOnBackPressed(a) {
        // TODO Something}})Copy the code

Later, through the test, so easy, the above few lines of code to solve the problem.

However, the test later told me that the Fragment Activity could not return. According to the breakpoint detection, the function that returns Activity#onKeyUp always returns true, which made me confused. Event distribution, how to return event interception? OnBackPressedDispatcher#onBackPressed

@MainThread
public void onBackPressed() {
    Iterator<OnBackPressedCallback> iterator =
            mOnBackPressedCallbacks.descendingIterator();
    while (iterator.hasNext()) {
        OnBackPressedCallback callback = iterator.next();
        if (callback.isEnabled()) {
            callback.handleOnBackPressed();
            return; }}if(mFallbackOnBackPressed ! =null) { mFallbackOnBackPressed.run(); }}Copy the code

As you can see from the above code, if the OnBackPressedCallback#isEnabled function currently returns true, the current return event will be consumed, and there will be an exception to intercept the return event. Second, even if we use the fragments in the add, when you return to event listeners will OnBackPressedCallback set to true, FragmentManager# updateOnBackPressedCallbackEnabled will continue to modify this value, As a result, we may not accurately receive the message of the return event. For example, ViewPager2 container has this exception.

private void updateOnBackPressedCallbackEnabled() {
    // Always enable the callback if we have pending actions
    // as we don't know if they'll change the back stack entry count.
    // See handleOnBackPressed() for more explanation
    synchronized (mPendingActions) {
        if(! mPendingActions.isEmpty()) { mOnBackPressedCallback.setEnabled(true);
            return; }}// This FragmentManager needs to have a back stack for this to be enabled
    // And the parent fragment, if it exists, needs to be the primary navigation
    // fragment.
    mOnBackPressedCallback.setEnabled(getBackStackEntryCount() > 0
            && isPrimaryNavigation(mParent));
}
Copy the code

So if the Fragment wants to listen for return events and uses the OnBackPressedCallback abstract class on the Fragment, the application scenario is that there is only one Fragment in the container and that Fragment does not use Navigation or Navigation.

In summary, it is not recommended to be used in the projectOnBackPressedCallbackTo implement return event listening.

Android 10Scoped StorageFill in the pit

Recently there was a requirement in the project to select a photo from the album to display and upload. Implementation is very simple, a few lines of code OK.

// ① Declare an ActivityResultLauncher variable
private lateinit var mLauncher: ActivityResultLauncher<Intent>
// initialize ActivityResultLauncher
override fun onAttach(context: Context) {
        super.onAttach(context)
        mLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            // Process the image}}// ③ Use ActivityResultLauncher to select an image
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI).apply {
	setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
}
mLauncher.launch(intent)
Copy the code

After getting the picture, I will display it normally. OK, let’s start the next step — uploading the server. However, since Android 10 has changed its storage policy to partition storage, we cannot change the URI to PATH directly yet, otherwise we will report permission denial exception when retrieving files.

java.io.FileNotFoundException: ** open failed: EACCES (Permission denied)

At this point we can add for an android: requestLegacyExternalStorage = “true” logo, as follows:

<application
	android:name=".AppApplication"
	android:allowBackup="false"
	android:icon="@mipmap/ic_logo"
	android:label="@string/app_name"
	android:networkSecurityConfig="@xml/network_security_config"
	android:supportsRtl="true"
	android:requestLegacyExternalStorage="true"
	android:theme="@style/base_AppTheme"
	tools:ignore="UnusedAttribute">
Copy the code

This is enough, but for the sake of caution, we’ll add the FileProvider.

<application
    .
    >
    
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepath" />
</provider>

<application/>
Copy the code

Then create a new XML directory under RES, and then a new ilepath file


      
<paths  xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
</paths >
Copy the code

Set read/write permissions on the Intent that holds the URI:

 it.data? .let { intent -> intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION intent.data? .let { uri ->// uri transfers files and uploads images}}Copy the code

Is that all? RequestLegacyExternalStorage is always temporary, Google now market has requirement on application target version must be 30, so still fit better. So how to fit? After testing, you can directly stream the file to the application private directory, and then you can freely obtain the file, the implementation method is as follows:

/** * copy the image to the project private directory */
fun uriToFIle(context: Context,uri: Uri):File? {val imgFile:File = context.getExternalFilesDir("image")? :context.filesDirif(! imgFile.exists()){ imgFile.mkdir() }try {
        val file =  File(imgFile.absolutePath + File.separator +
                System.currentTimeMillis() + ".jpg")
        // Get the byte input stream using the openInputStream(URI) method
        val fileInputStream = context.contentResolver.openInputStream(uri)!!
        val fileOutputStream =  FileOutputStream(file)
        val buffer = ByteArray(1024)
        var byteRead = 0
        while (-1! = fileInputStream.read(buffer).also { byteRead = it }) { fileOutputStream.write(buffer,0, byteRead)
        }
        fileInputStream.close()
        fileOutputStream.flush()
        fileOutputStream.close()
        Log.e("uriToFIle"."work done")
        return file
        File.getabsolutepath ()
    } catch (e:Exception ) {
        e.printStackTrace()
        return null}}// Call mode
valfile = Luban.with(context).load(FileUtil.uriToFIle(context, uri)!!) .ignoreBy(256).get().first()
Copy the code

Note: (1) There is a problem with Luban’s compressed URI image. Avoid supplying uri to Luban directly. ② uriToFIle function is ugly, throw a brick to the door!

13,ViewPager2#AdapterLead to collapse

The background situation is that there are three tabs at the bottom of the home page — home, Bills, and mine. Now the requirement is that the user who is not logged in to the home page displays the UI that is not logged in, and clicks bills and my two tabs to enter the login page. So here’s the code:

viewpager.adapter = object : FragmentStateAdapter(this@MainActivity){ override fun getItemCount() = 3 override fun createFragment(position: Int):Fragment{ return if(! UserInfoManager.getInstance().isLogin){ list[3] }else{ list[position] } } }Copy the code

The results yielded the following anomalies:

java.lang.IllegalStateException: Fragment already added
        at androidx.fragment.app.Fragment.setInitialSavedState(Fragment.java:709)
        at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:269)
        at androidx.viewpager2.adapter.FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:175)
        at androidx.viewpager2.adapter.FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:67)
        at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
        at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
        at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:527)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:1099)
        at android.view.View.layout(View.java:23750)
        at android.view.ViewGroup.layout(ViewGroup.java:7277)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3709)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3161)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2222)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9123)
Copy the code

GetItemId = getItemId = getItemId

.override fun getItemId(position: Int): Long {
    return if(position! =0){
        position*100L
    }else{
        if(! UserInfoManager.getInstance().isLogin){3*1000L
        }else{
            50*1000L}}}...Copy the code

Return ItemCount to 4 and createFragment function position to Fragment

viewpager.adapter = object : FragmentStateAdapter(this@MainActivity) {override fun getItemCount(a) = 4
    override fun createFragment(position: Int) = list[position]
}
Copy the code

14,bundletoolparsingaabPost run crash

As we all know, Starting September 1, 2021, Google will require the Android App Bundle to be available as a new installation package. So we will test it after packaging, and then put it on the shelves if there is no problem. Then, last year, I encountered a difficult problem, that is, after the project packaged APK installation can be used normally, but after the bundletool parse into APK, MMKV initialization crash occurred during the run, the log is as follows:

Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.runner.jar", zip file "/data/app/com.chehejia.oc.m01-TBWDg7lrljgGQwQnlsmgcQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.chehejia.oc.m 01-TBWDg7lrljgGQwQnlsmgcQ==/lib/arm64, /data/app/com.chehejia.oc.m01-TBWDg7lrljgGQwQnlsmgcQ==/base.apk! /lib/arm64-v8a, /system/lib64, /vendor/lib64, /product/lib64]]] couldn't find "libmmkv.so"Copy the code

Later, it was found through the test that there was no problem with the AAB uploaded through GooglePlay and the APK file corresponding to the mobile phone of the specified model and general model. So I thought bundletool was buggy, and GooglePlay used an optimized version of the parsing tool, so I didn’t care. Later, I saw that someone recommended using ReLinker to solve the above problem. The code is as follows:

MMKV.initialize(filesDir.absolutePath + "/mmkv") { libName -> ReLinker.loadLibrary(this@App, libName) }
Copy the code

This solves the above problem. So again, what does the library do to solve this problem?

This confirmed that Google Play was not the issue, the issue was with Android’s PackageManager installation process. At some point during the installation, something would go wrong and the native libraries inside of the APK would not be extracted.

This confirms that Google Play is not the problem, the problem is the Android PackageManager installation process. At some point during the installation process, there will be a problem with extracting the local libraries within the APK.

We started to log the installer package name in our crashes and quickly figured out that, yes, users were installing the app from various sources, and each new UnsatisfiedLinkError was coming from a manually installed app where the user mistakenly installed the wrong flavor for their device’s architecture. This was the final “gotcha”, and we were relieved that it had a very simple explanation.

We started recording installation package names in crashes and quickly discovered that, yes, users were installing applications from a variety of sources, and that every new UnsatisfiedLinkError came from a manually installed application, and that users had mistakenly installed the wrong architecture for their devices. This is the final “trap,” and we are relieved that it has a very simple explanation.

The Perils of Loading Native Libraries on Android

It turns out that when we go through the non-system installation, PackageManager mistakenly chose abi architecture (such as ArmeABI, ARM64-V8A and other architectures), so the code from the installation and architecture to find two aspects, to solve this problem.


Afterword.

1—- This article is compiled by Su Can, and will be updated periodically.

2—- If there is anything you want to communicate, please leave a comment. You can also add wechat :Vicent_0310

3—- personal ability is limited, if there is any improper place welcome to criticize and point out, must be humbly correct