The phenomenon of

Recently, a weird Bug appeared in the project. The test student found that the login status was cleared after completing a certain business process. After receiving the problem, we tried to reproduce it in the first time, but it was not successful.

Analysis of the positioning

During the development, we used SharedPreferences (hereinafter referred to as Shared) provided by THE SDK for data persistence. In the buggy code, there was no operation to clear the login flag, so I raised the suspicion that there was a problem in one operation of Shared, which led to all data being cleared. But because has not been able to reappear in the test machine, so has not been able to locate the cause. Until recently, we used the same phone with the Bug and then reproduced it after a “view” operation.

Based on the above information, we locate the “view” function code and find that null is used as the key when operating on Shared data. The login information is cleared when the application process is killed and re-enters the system. About the problem that cannot be reproduced on the test machine. After verification, it was found that this problem only occurs under version 5.0. It seems that the processing should be done after 5.0.

explore

Bugs are fixed, but we always advocate knowing why. So I wrote a Demo to see what happened. The interface is simple, with only two buttons:





interface

The Activity code is as follows:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create a SharedPreferences named fenglx The mode is MODE_PRIVATE SharedPreferences SharedPreferences = getSharedPreferences("fenglx", MODE_PRIVATE); final SharedPreferences.Editor editor= sharedPreferences.edit(); SetOnClickListener (new view.onClickListener () {@override public void onClick(View) Editor. putString("key1","value1"); editor.putString("key2","value2"); editor.putString("key3","value3"); editor.commit(); }}); // button 2 findViewById(R.i.rite_null_btn).setonClickListener (new view.onClickListener () {@override public void Editor. putString(null,"value4"); editor.commit(); }}); }}Copy the code

After calling getSharedPreferences(), an XML is created in the /data/data/ package name /shared_prefs directory for persistent data. With the ADB command, you can view these XML files to see how the data changes under different operations. After the Demo application runs, the first step is to store three test data in Shared. Next, open the file named fenglx.xml with the ADB action as follows:





Command Line Screenshot



null







Command Line Screenshot


Do not Exit the application The Kill process enters again Kill process, write new data
In the XML The data are The data are Only new data
The code is read The data are Can’t read Only new data

From the above, I come to the conclusion that. In an application, parsing XML every time a Shared read is performed would be time-consuming. As you can see from the source code, Shared stores data in a Map at runtime. Thus, when the application starts, the program loads the XML parsing into memory and maps it to a Map. All subsequent reads and writes are operations on Map objects in memory. XML is manipulated only when the data needs to be updated. Shared data is lost, most likely because the XML was not successfully loaded into memory and subsequent operations erased the original data in the XML. This causes bugs like “login status cleared”.





Console exception



grepcode


551 public static final HashMap More readThisMapXml(XmlPullParser parser, String endTag, String[] name) 552 throws XmlPullParserException, java.io.IOException 553 { 554 HashMap map = new HashMap(); 555 556 int eventType = parser.getEventType(); 557 do { 558 if (eventType == parser.START_TAG) { 559 Object val = readThisValueXml(parser, name); 560 if (name[0] ! //!! Key code!! 561 //System.out.println("Adding to map: " + name + " -> " + val); 562 map.put(name[0], val); 563 } else { 564 throw new XmlPullParserException( 565 "Map value without name attribute: " + parser.getName()); 566 } 567 } else if (eventType == parser.END_TAG) { 568 if (parser.getName().equals(endTag)) { 569 return map; 570 } 571 throw new XmlPullParserException( 572 "Expected " + endTag + " end tag at: " + parser.getName()); 573 } 574 eventType = parser.next(); 575 } while (eventType ! = parser.END_DOCUMENT); 576 577 throw new XmlPullParserException( 578 "Document ended before " + endTag + " end tag"); 579}Copy the code

Name [0] == null throws XmlPullParserException. So 5.0 is fault tolerant processing, the next is 5.0 source code:

774 public static final HashMap<String, ? > More ... readThisMapXml(XmlPullParser parser, String endTag, 775 String[] name, ReadMapCallback callback) 776 throws XmlPullParserException, java.io.IOException 777 { 778 HashMap<String, Object> map = new HashMap<String, Object>(); 779 780 int eventType = parser.getEventType(); 781 do { 782 if (eventType == parser.START_TAG) { 783 Object val = readThisValueXml(parser, name, callback); 784 map.put(name[0], val); 785 } else if (eventType == parser.END_TAG) { 786 if (parser.getName().equals(endTag)) { 787 return map; 788 } 789 throw new XmlPullParserException( 790 "Expected " + endTag + " end tag at: " + parser.getName()); 791 } 792 eventType = parser.next(); 793 } while (eventType ! = parser.END_DOCUMENT); 794 795 throw new XmlPullParserException( 796 "Document ended before " + endTag + " end tag"); 797}Copy the code

If (name[0]! = null). Therefore, when the Key is null, data is not affected to be loaded into memory.

The problem summary

To summarize, the only difference between the two versions of the source code is that the 4.4.4 version nulls the Key value when parsing XML. If there is a null value, the data cannot be loaded into memory smoothly. This leads to a more serious problem, as the old data cannot be loaded into memory, new data store operations are based on the new Map, and the old data is erased when XML is written. The loss of data is catastrophic. So Google fixed this issue after 5.0. SharedPreferences is a very common way of persisting data, and developers should avoid using NULL as a Key, even if it is legal. In this case, we inadvertently overlooked the robustness of the code. Hope everyone in the development, pay attention to this problem, avoid “penny wise and pound foolish”.