美文网首页Android 开发笔记AndroidAndroid技术知识
Android开发中小问题汇总(持续更新)

Android开发中小问题汇总(持续更新)

作者: 闲庭 | 来源:发表于2017-07-08 20:57 被阅读499次

Android开发中小问题汇总(持续更新),此排序没有任何优先级或者重要程度。

此笔记只为记录平时开发中碰到的经常用到确不太注意的一些问题,每次用过就忘记,还要重新搜索解决方案,所以在此积累下平时开发中碰到的一些常用而又容易忘记的简单小bug。

  1. Android 如何让EditText不自动获取焦点。
    场景:有时候我们需要界面初始化时候并不希望EditText自动获取焦点,因为自动获取焦点会弹出软键盘,此种场景并不是我们所需要的,所以我们要设置让EditText不自动获取焦点
    解决方案:在EditText的父级控件中找一个,设置成如些:

    android:focusable="true"     
    android:focusableInTouchMode="true"
    

    这样,就把EditText默认的行为拦截了。
    或者采用下面这种方式:

     EditText对象的clearFocus();
     InputMethodManager imm = 
     (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
     imm.hideSoftInputFromWindow(editMsgView.getWindowToken(), 0); //关闭软键盘
    
  2. Android 禁止初始时ScrollView自动滚动到底部。
    场景:用ScrollView,加载数据时如果数据超过一屏的高度,有时会出现ScrollView自动滚动到底部,可能不是我们所需要的,我们可能需要的是从顶部开始显示。
    解决方案:在ScrollView子标签LinearLayout里面加上:

    android:focusable="true"  
    android:focusableInTouchMode="true"
    

    如果出现某个控件抢占焦点造成的,可以禁止此控件的焦点。

  3. Android 软键盘弹出时把原来布局顶上去的解决方法。
    场景:键盘弹出时,会将布局底部的导航条顶上去。
    解决方案:在mainfest.xml中,在和导航栏相关的activity中添加如下代码:

    <activity
    android:name=".MainActivity"
    android:windowSoftInputMode="adjustResize|stateHidden"
    />
    

    扩展:

    【A】stateUnspecified:软键盘的状态并没有指定,系统将选择一个合适的状态或依赖于主题的设置
    【B】stateUnchanged:当这个activity出现时,软键盘将一直保持在上一个activity里的状态,无论是隐藏还是显示
    【C】stateHidden:用户选择activity时,软键盘总是被隐藏
    【D】stateAlwaysHidden:当该Activity主窗口获取焦点时,软键盘也总是被隐藏的
    【E】stateVisible:软键盘通常是可见的
    【F】stateAlwaysVisible:用户选择activity时,软键盘总是显示的状态
    【G】adjustUnspecified:默认设置,通常由系统自行决定是隐藏还是显示
    【H】adjustResize:该Activity总是调整屏幕的大小以便留出软键盘的空间
    【I】adjustPan:当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分
    
  4. 在Android 6.0下继续使用HttpClient。
    HttpClient在Android 6.0已经被Google废除了,如果想继续使用HttpClient的话,只需在主Module的gradle中配置:

    android {  
        useLibrary ‘org.apache.http.legacy‘  
    }  
    
  5. android 6.0以上运行时权限问题

    • 报错如下:
    java.lang.RuntimeException: 
    Unable to start activity    ComponentInfo{com.liujc.supereader/com.liujc.supereader.ui.activity.ReadActivity}: java.lang.SecurityException: com.liujc.supereader was not granted  this permission: android.permission.WRITE_SETTINGS.
    
    • 解决方案:Android 中有两个特殊权限,使用requestPermission方法是不成功的,需要特殊设置
      • SYSTEM_ALERT_WINDOW
      • WRITE_SETTINGS

    关于这两个权限,需要我们自己手动开启系统设置的Activity界面。后来打开系统设置页面,才有这个权限的设置,设置下就ok了。
    友好方式就是检测到该权限获取失败,提示用户跳转到设置界面设置:

    • 设置弹框权限
     //权限申请相关方法
    private static final int REQUEST_CODE = 1;
    private void requestAlertWindowPermission() {    
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);  
            intent.setData(Uri.parse("package:" + getPackageName())); 
            startActivityForResult(intent, REQUEST_CODE);             
    }
    //回调
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);    
        if (requestCode == REQUEST_CODE) {        
             if (Settings.canDrawOverlays(this)) {            
                  Toast.makeText(this,"弹窗权限开启!",Toast.LENGTH_SHORT).show();            
                  PrefUtils.setBoolean(MainActivity.this, "isAllowAlert", true);        
              }else {           
                  PrefUtils.setBoolean(MainActivity.this, "isAllowAlert", false);        
                 }    
           }
      }
    
    • 设置权限
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 判断是否有WRITE_SETTINGS权限
            if(!Settings.System.canWrite(this)) {
                // 申请WRITE_SETTINGS权限
                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
                        Uri.parse("package:" + getPackageName()));
                startActivityForResult(intent, REQUEST_CODE);
            } else {
                dosomething();
            }
        } else {
            dosomething();
        }
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intentdata) {
            if (requestCode == REQUEST_CODE) {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                 // 判断是否有WRITE_SETTINGS权限
                 if (Settings.System.canWrite(this)) {
                     dosomething();
                 }
             }
         }
         super.onActivityResult(requestCode, resultCode, data);
     }
    
  6. 在使用toolbar时报以下错误:
    This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR
    主要问题出在我们配置的style中:
    错误写法:

    <style name="AppTheme.NoActionBar">
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
     </style>
    

    其中AppTheme使用的主题是AppCompat的主题,由于AppCompat主题下的windowActionBar和windowNoTitle的命名方式前都没有android字样,所以报错。
    正确写法:

    <style name="AppTheme.NoActionBar">
         <item name="windowActionBar">false</item>
         <item name="windowNoTitle">true</item>
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
     </style>
    
  7. Android Studio导入项目报:错误: 非法字符: '\ufeff'。
    出现场景:
    复制的Eclipse项目的源文件粘贴到Android Studio上,在Android Studio上面编译运行报错:“错误: 非法字符: ‘\ufeff’”。
    原因:
    Eclipse可以自动把UTF-8+BOM文件转为普通的UTF-8文件,但AndroidStudio需要重新转一下 。
    解决办法:
    将编码格式UTF-8+BOM文件转为普通的UTF-8文件。

    • 简单方法,在AS右下角,将编码改为GBK,再转为UTF-8,可以解决。 这里写图片描述
    • 用EditPlus
      将文件用EditPlus打开,然后选择Document(文件),再选择Convert Encoding(编码转换)成UTF-8即可。将修改过编码格式的类,拷到项目中,覆盖原先的类。
  8. Android Studio出现Error:No service of type Factory available in ProjectScopeServices.
    出现场景:
    当你从第三方download一个项目时,可能用Android Studio去打开想运行下看看demo的运行效果,结果很不爽,碰到个error:No service of type Factory available in ProjectScopeServices.
    解决办法:
    定位到根目录(也就是Project)下的build.gradle文件中的buildscript —> dependencies中添加
    classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'编译下应该就OK了。
    具体如下:

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.2.3'
            classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 
        }
    }
    
  9. 使用Genymotion调试出现错误INSTALL_FAILED_CPU_ABI_INCOMPATIBLE
    出现场景:
    在Android模拟器上安装apk调试程序时,出现以下异常:

    Installation failed with message INSTALL_FAILED_NO_MATCHING_ABIS. It is possible that this issue is resolved by uninstalling an existing version of the apk if it is present, and then re-installing.
    WARNING: Uninstalling will remove the application data!
    Do you want to uninstall the existing application?
    

    调查原因:
    由于程序中使用了native libraries(也就是.so文件) 。该native libraries 不支持当前的cpu的体系结构。
    现在安卓模拟器的CPU/ABI一般有三种类型,INTEL X86,ARM,MIPS。
    解决方案:
    根据当前模拟器的cpu体系结构在程序目录中提供不同目录下的so文件,如果程序中只提供了ARM,出现INSTALL_FAILED_NO_MATCHING_ABIS的错误,那么久改用ARM,否则反之。再或者你全部添加,如下图:

    image.png
  10. Android 7.0调用系统相机拍照崩溃问题
    场景分析:
    之前也许我们认为Android 6.0以后加了动态获取权限问题,我们加了对应权限问题然后调用下面代码就可以调用系统相机拍照。

    File tempFile = new File(photoFile, pickPicName);
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
     // 从文件中创建uri
    Uri uri = Uri.fromFile(tempFile);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUSTCODE);
    

    上面代码是常见的打开系统相机拍照的代码,拍照成功后,照片会存储在tempFile文件中。这段代码在Android 7.0系统之前使用没有问题,可以正常调用系统相机拍照问题,但如果运行在7.0系统上就会崩溃,抛出android.os.FileUriExposedException异常。
    调查原因:
    Android 7.0系统强制启用了被称作 StrictMode的策略,禁止不安全路径被外部访问(禁止向您的应用外公开 file://URI),若要在应用间共享文件,您应发送一项 content://URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider类。如需了解有关权限和共享文件的详细信息,请参阅共享文件
    解决方案:
    下面就针对7.0系统采用共享文件的形式:content:// URI去调用系统相机拍照,完整代码如下:

    File tempFile = new File(photoFile, pickPicName);
      //获取系統版本
      int currentapiVersion = android.os.Build.VERSION.SDK_INT;
      // 激活相机
      Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
      if (currentapiVersion < Build.VERSION_CODES.N) {
          // 从文件中创建uri
          Uri uri = Uri.fromFile(tempFile);
          intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
      } else {
          //兼容android7.0 使用共享文件的形式
          ContentValues contentValues = new ContentValues(1);
          contentValues.put(MediaStore.Images.Media.DATA, tempFile.getAbsolutePath());
          Uri uri = AssistantApplication.getContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
          intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
      }
      startActivityForResult(intent, CAMERA_REQUSTCODE);
    
  11. List集合中contains方法总是返回false
    出现场景:
    在利用ArrayList类的caontains方法时,新建一个User类(一个Javabean),然后写了一个存放User类的ArrayList 但在调用list.contains(user)时总是返回false。
    调查原因:
    查看ArrayList的源码,其contains方法源码如下:

    /**
    * Searches this {@code ArrayList} for the specified object.
    *
    * @param object
    *            the object to search for.
    * @return {@code true} if {@code object} is an element of this
    *         {@code ArrayList}, {@code false} otherwise
    */
    @Override public boolean contains(Object object) {
      Object[] a = array;
      int s = size;
      if (object != null) {
          for (int i = 0; i < s; i++) {
              if (object.equals(a[i])) {
                  return true;
              }
          }
      } else {
          for (int i = 0; i < s; i++) {
              if (a[i] == null) {
                  return true;
              }
          }
      }
      return false;
    }
    

    发现在contains方法会调用 object.equals(a[i])方法,其中a[i]是个Object类的实例。也就是说我在调用list.contains(user)时实际上比较的是user.equals(a[i])。也就是这里一直返回false。下面我们来通过拓展知识"=="和equals方法究竟有什么区别?来看一下具体什么原因导致的。
    拓展知识:"=="和equals方法究竟有什么区别?

    • ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符
      如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内 存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存(堆内存),此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存(堆内存)的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。
    • equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如,对于下面的代码:
      String a=new String("foo");
      String b=new String("foo");
      两条new语句创建了两个对象,然后用a/b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值(对应对象的首地址)是不相同的,所以,表达式a==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。

    如果一个类没有自己定义equals方法(就比如之前创建的User类),那么它将继承Object类的equals方法,Object类的equals方法的实现源代码如下:

    public boolean equals(Object o) {
         return this == o;
     }
    

    这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object类继承的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。
    现在再回过头来看看user.equals(a[i])为什么一直返回false,因为这里的a[i]和user是两个独立的对象,并且User类也是继承Object类的equals方法,所以一直返回false。
    解决方案:
    如果希望能够比较该User类创建的两个实例对象的内容是否相同,那么必须在User类中覆盖equals方法(不再是继承Object类的equals方法)即可。代码如下:

    public boolean equals(Object obj) { 
      if (obj instanceof User) {   
          User u = (User) obj;   
          return this.username.equals(u.username)   
                  && this.password.equals(password);   
      }   
      return super.equals(obj); 
    }
    
  12. 在android 4.0.3上对sdcard不能创建目录或文件。
    出现场景:
    之前都在4.4以后运行的程序,发现可以在本地创建对应的数据库文件或者其他日志文件,然而运行到了Android 4.0.3上却崩溃了,提示我文件目录找不到,也真是纳闷了,没办法碰到问题得解决啊。
    调查原因:
    刚开始以为自己权限没有申请,打开manifest发现权限已申请,如下:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    

    那肯定不是权限的问题了,所以那就是获取路径上面出现问题了。
    所以这里就要看看/storage/sdcard/sdcard/mnt/sdcard 三者的区别了。

    • /sdcard是/mnt/sdcard的符号链,指向/mnt/sdcard,
    • /storage/sdcard 是sdcard的分区……
      /sdcard/:这是一个软连接,指向以下:
      • /mnt/sdcard (Android < 4.1)
      • /storage/sdcard0 (Android 4.1+)
        参考以下内容:
    • /storage/emulated/0/: to my knowledge, this refers to the "emulated MMC" ("owner part"). Usually this is the internal one. The "0" stands for the user here, "0" is the first user aka device-owner. If you create additional users, this number will increment for each.
    • /storage/emulated/legacy/ as before, but pointing to the part of the currently working user (for the owner, this would be a symlink to /storage/emulated/0/). So this path should bring every user to his "part".
    • /sdcard/: According to a comment by Shywim, this is a symlink to...
      • /mnt/sdcard (Android < 4.1)
      • /storage/sdcard0 (Android 4.1+)
    • /storage/sdcard0/: As there's no legacy pendant here (see comments below), the "0" in this case rather identifies the device (card) itself. One could, eventually, connect a card reader with another SDCard via OTG, which then would become /storage/sdcard1 (no proof for that, just a guess -- but I'd say a good one)

    解决方案:
    将获取本地SD卡目录的代码改成以下:

      public static String getSDPath() {
        boolean sdCardExist = Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);
        if (sdCardExist) {
            return Environment.getExternalStorageDirectory().toString();
        } else {
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1){
                File sdPath = new File("/mnt/sdcard2");
                if(sdPath != null && sdPath.exists()) {
                    return sdPath.getAbsolutePath();
                }
            }
            return "";
        }
    }
    

    这里之所以指定目录为/mnt/sdcard2,是因为Android 4.0.3系统的手机在无本地内存卡是对应的本地目录为/mnt/sdcard2,而当插入内存卡时,对应的内存卡目录为/mnt/sdcard,而手机的本地目录依然是/mnt/sdcard2,所以这里硬编码写死了,如果有的手机还是没办法使用,请自行修改。

  13. databinding在android studio2.3版本后不再默认支持使用
    出现场景:
    由于项目中用到了databinding,在android studio 2.2.3上没有任何问题,然而在android studio2.3版本后却不再默认支持使用,编译时会提示com.xxx.xxx.databinding不存在。
    解决方案:
    需要在项目的app目录下的build.gradle中的dependencies里面添加apt 'com.android.databinding:compiler:2.3.0',此时编译即可通过,可正常使用。
    注意:
    在部分手机上(如红米和小米)报错如下:Android app installation: Unknown failure (Failure - not installed for 0)
    解决方案:

    For Redmi and Mi devices turn off MIUI Optimization and reboot your phone.
    Settings > Additional Settings > Developer Options > MIUI Optimization
    

    即:将红米和小米设备关闭MIUI优化功能然后重启手机,
    设置->更多设置->开发者选项->启用MIUI优化(设置成关闭)

  14. Android Studio 2.3版本出现警告:Warning:Using incompatible plugins for the annotation processing: android-apt. This may result in an unexpected behavior.
    Android Studio Warning: Using incompatible plugins for the annotation processing


软件使用链接:

  1. 点击 Charles 的顶部菜单,选择 “Help” –> “SSL Proxying” –> “Install Charles Root Certificate”.


    1.png
  2. 用手机在浏览器中打开http://charlesproxy.com/getssl 下载及安装证书。此时可以拦截http请求了,不过如果拦截https请求时,会出现乱码,那就接着往下看。
  3. 即使是安装完证书之后,Charles 默认也并不截取 Https 网络通讯的信息,如果你想对截取某个网站上的所有 Https 网络请求,可以在该请求上右击,选择Enable SSL Proxying,此时即可拦截手机的https请求数据。

相关文章

网友评论

    本文标题:Android开发中小问题汇总(持续更新)

    本文链接:https://www.haomeiwen.com/subject/vmcstxtx.html