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

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

【Android开发中小问题汇总目录】
【Android开发中小问题汇总二】

  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方法究竟有什么区别?可参考【简单解读equals()与“==”、hashcode()的关系】。

    • ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符
      如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内 存(堆内存),变量也占用一块内存,例如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. ScrollView嵌套RecyclerView出现item显示不全的问题
    解决方案:
    在RecyclerView上再嵌套一层RelativeLayout然后添加属性 android:descendantFocusability="blocksDescendants".
    该属性android:descendantFocusability的含义是:当一个view获取焦点时,定义ViewGroup和其子控件两者之间的关系。
    它一共有3个属性值,它们分别是:

    beforeDescendants:viewGroup会优先子类控件而获取焦点
    afterDescendants:viewGroup只有当子类控件不需要获取焦点的时候才去获取焦点
    blocksDescendants:viewGroup会覆盖子类控件而直接获取焦点
    

    代码示例如下:

    <ScrollView
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:scrollbars="none">
        <RelativeLayout
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:descendantFocusability="blocksDescendants"
              android:paddingTop="20dp">
              <android.support.v7.widget.RecyclerView
                   android:id="@+id/rv_home_config"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent"
                   android:clipToPadding="false"/>
         </RelativeLayout>
    </ScrollView>
    
  13. databinding在android studio2.3版本后不再默认支持使用
    出现场景:
    由于项目中用到了databinding,在android studio 2.2.3上没有任何问题,然而在android studio 2.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.
    出现场景:
    如果你的应用一些如ButterKnife,dagger等的开源注解框架的流行,那么应该就用了APT,那么什么是APT呢?
    APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码。
    所以没有apt,onClick绑定不了监听。并且Apt工具的作者宣布了不再维护该工具了,而且Android Studio也有了自己的插件,并且可以通过gradle来简单的配置。
    当Android studio升级到2.3及以上版本,Android Gradle 插件升级到2.3.3时,运用apt时就会出现以下警告:
    Warning:Using incompatible plugins for the annotation processing: android-apt. This may result in an unexpected behavior.
    解决方案:
    android-apt是由一位开发者自己开发的apt框架,源代码托管在这里,随着Android Gradle 插件 2.2 版本的发布,Android Gradle 插件提供了名为 annotationProcessor 的功能来完全代替 android-apt ,自此android-apt 作者在官网发表声明最新的Android Gradle插件现在已经支持annotationProcessor,并警告和或阻止android-apt ,并推荐大家使用 Android 官方插件annotationProcessor。annotationProcessor是APT工具中的一种,他是google开发的内置框架,不需要引入,可以直接在build.gradle文件中使用。
    如果想替换为annotationProcessor,那就要知道android-apt是如何使用的。

    1. 添加android-apt到Project下的build.gradle中
        //配置在Project下的build.gradle中
      buildscript {
         repositories {
             mavenCentral()
         }
      dependencies {
       //替换成最新的 gradle版本
       classpath 'com.android.tools.build:gradle:2.2.3'
       //替换成最新android-apt版本
       classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        }
      }
      
    2. 在Module中build.gradle的配置
      dependencies {
           compile 'com.jakewharton:butterknife:8.4.0'
          apt 'com.jakewharton:butterknife-compiler:8.4.0'
      }
      

    所以想用annotationProcessor替代android-apt。删除和替换相应部分即可。步骤如下:

    1. 移除配置在Project下的build.gradle中的android-apt。
      即移除classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'.
    2. 移除在Module中build.gradle的配置。
      即移除apply plugin: 'android-apt'.
    3. 修改Module中build.gradle中的dependencies中将 apt修改为annotationProcessor。
      即如下:
      dependencies {
         compile 'com.jakewharton:butterknife:8.4.0'
         annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
      }
      

    此时编译程序发现上述警告信息已经消失。
    扩展知识:

    • Provided 和annotationProcessor区别。
      1. annotationProcessor
        只在编译的时候执行依赖的库,但是库最终不打包到apk中,编译库中的代码没有直接使用的意义,也没有提供开放的api调用,最终的目的是得到编译库中生成的文件,供我们调用。
      2. Provided
        Provided 虽然也是编译时执行,最终不会打包到apk中,但是跟apt/annotationProcessor有着根本的不同。

    A 、B、C都是Library。
    A依赖了C,B也依赖了C
    App需要同时使用A和B
    那么其中A(或者B)可以修改与C的依赖关系为Provided

    A这个Library实际上还是要用到C的,只不过它知道B那里也有一个C,自己再带一个就显得多余了,等app开始运行的时候,A就可以通过B得到C,也就是两人公用这个C。所以自己就在和B汇合之前,假设自己有C。如果运行的时候没有C,肯定就要崩溃了。
    也就是说Provided是间接的得到了依赖的Library,运行的时候必须要保证这个Library的存在,否则就会崩溃,起到了避免依赖重复资源的作用。

  15. 关于在fragment切换时应用崩溃的问题,报错如下:
    java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class android.os.Bundle instead. This usually happens when two views of different type have the same id in the same hierarchy. This view's id is id/center_progress_webview. Make sure other views do not use the same id.
    出现场景:
    在一个主的FragmentActivity中包括FragmentA,FragmentB,FragmentC,FragmentC中有一个自定义的WebView,当FragmentA,FragmentB和FragmentC之间切换时,程序就会崩溃,并报上述的错误。
    解决方案:
    需要重写自定义WebView中的onRestoreInstanceState函数,修改如下即可:

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        try {
            super.onRestoreInstanceState(state);
        }catch (Exception e) {}
        state=null;
    }
    
  16. Android BottomSheetDialog设置背景透明问题的解决办法
    出现场景:
    在使用BottomSheetDialog实现底部弹出框时,需要在自定义布局中设置上半部分控件一半为透明,可是怎么在布局中设置都无效,总是没办法显示透明。
    解决办法:

    **对BottomSheetDialog设置如下属性  **
     mBottomSheetDialog.getWindow().findViewById(R.id.design_bottom_sheet) .setBackgroundResource(R.color.transparent_color);
    
    **如果需要去除灰色遮罩层:**
    mBottomSheetDialog.getWindow().setDimAmount(0f)
    
  17. Iconfont在Android中的使用
    阿里提供的Iconfont-国内功能很强大且图标内容很丰富的矢量图标库,提供矢量图标下载、在线存储、格式转换等功能。
    如何使用:

    • 从iconfont平台选择要使用到的图标
    • 下载代码,把iconfont.ttf文件导入到项目中的assets中的iconfont文件夹中
    • 用TextView代替ImagerView,找到图标相对应的 HTML 实体字符码给textView设置
    • textview设置大小跟颜色,图标的大小颜色也会改变
    • 为Textview设置指定的ttf文字
    <TextView
        android:id="@+id/icon_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:textColor="@color/red"
        android:textSize="50dp"/>
    
    //为TextView设置指定ttf文字
    Typeface iconfont = Typeface.createFromAsset(getAssets(), "iconfont/iconfont.ttf");
    TextView textview = (TextView)findViewById(R.id.icon_text);
    textview.setTypeface(iconfont);
    

    上述方法可以使用iconfont了,但是每次都给TextView设置指定setTypeface是不是也很繁琐,而且一直不断的在读取iconfont.ttf文字,也很浪费内存,所以就想到封装一个工具类。代码如下:

    public class FontHelper {
        public static final String DEF_FONT = "iconfont/iconfont.ttf";
    
        public static final void injectFont(View rootView) {
            injectFont(rootView, Typeface.createFromAsset(rootView.getContext().getAssets(),
                DEF_FONT));
        }
    
        private static void injectFont(View rootView, Typeface typeface) {
            if (rootView instanceof ViewGroup) {
                ViewGroup viewGroup = (ViewGroup) rootView;
                int childViewCount = viewGroup.getChildCount();
                for (int i = 0; i < childViewCount; i++) {
                    injectFont(viewGroup.getChildAt(i), typeface);
                }
            } else if (rootView instanceof TextView) {
                ((TextView) rootView).setTypeface(typeface);
            }
        }
    }
    

    这样我们每次调用FontHelper.injectFont(textview)就可以了,你可能会说这还不是我想要的,我连这行代码都不想多写,那好,接着往下看:我们可以自定义一个TextView然后初始化时setTypeface即刻,代码如下:

    public class TextViewIcon extends AppCompatTextView {
        public TextViewIcon(Context context) {
            super(context);
            init(context);
        }
        public TextViewIcon(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
        public TextViewIcon(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
        private void init(Context context) {
            setTypeface(Typeface.createFromAsset(context.getAssets(),"iconfont/iconfont.ttf"));
        }
    }
    

    现在我们在布局文件中写如下代码即可:

    <com.xxx.xxx.TextViewIcon 
        android:id="@+id/icon_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:textColor="@color/red"
        android:textSize="50dp"/>
    

    可是我也想实现像普通textview动态改变文字一样动态改变iconfont的效果怎么办呢?也就是说在代码里动态的调整图标的大小颜色或改变图片,iconfont改变大小颜色这很简单直接调用TextView的setTextSize和setTextColor就可以了,动态设置图片是不是setText呢?
    textview.setText("");
    你会发现这并不会如你所愿显示对应的图片效果,因为这里涉及到unicode 字符的问题。所以将"&#x" 替换成 "\u",用 unicode 字符来表示,即代码如下:
    textview.settext("\ue66e");

  18. Scrollview 自动滚动到顶部或者底部

    • 设置默认滚动到顶部
    scrollView.post(new Runnable() {
     @Override
     public void run() {
     // TODO Auto-generated method stub
         scrollView.fullScroll(ScrollView.FOCUS_UP);
     }
    });
    
    • 设置默认滚动到底部
    scrollView.post(new Runnable() {
    @Override
    public void run() {
    // TODO Auto-generated method stub
          scrollView.fullScroll(ScrollView.FOCUS_DOWN);
    }
    });
    
  19. 在对原有项目重构时,进行代码迁移,涉及到so文件代码中报下面错误:

      java.lang.UnsatisfiedLinkError: dlopen failed: /data/app/com.aihuishou.qualitycheckphotos-1/lib/arm/libzbarjni.so: has text relocations
    

    解决方法:
    这个libiconv.so(xx.so)文件使用了较低版本的SDK,当时我的targetSdkVersion为25,所以我就降低到了22,就不会再报错了,而且能够正常使用了。 这是libiconv.so文件的解决办法,如果你用的那个xx.so文件降低到22还报错的话,建议继续降低版本尝试。

  20. android studio adb连接不上手机调试。
    android stuido 连接真机能运行但是不能调试,通常跟某些手机软件有关。

    1. 连不上手机时先查看端口是否能被绑定,使用cmd命令adb nodaemon server
      如果提示下面问题,是端口绑定失败:

         error: could not install *smartsocket* listener: cannot bind to 127.0.0.1:5037: 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
      
    2. 则继续查看到底是哪个端口给占用了,运行命令:netstat -ano | findstr "5037",结果输出如下:

    D:\software\AndroidTools\sdk\platform-tools>netstat -ano | findstr "5037"
         TCP    127.0.0.1:5037         0.0.0.0:0              LISTENING       16636
         TCP    127.0.0.1:5037         127.0.0.1:61903        TIME_WAIT       0
         TCP    127.0.0.1:5037         127.0.0.1:61904        TIME_WAIT       0
         TCP    127.0.0.1:5037         127.0.0.1:61912        TIME_WAIT       0
         TCP    127.0.0.1:5037         127.0.0.1:61913        TIME_WAIT       0
         TCP    127.0.0.1:5037         127.0.0.1:61923        TIME_WAIT       0
         TCP    127.0.0.1:5037         127.0.0.1:61924        TIME_WAIT       0
         TCP    127.0.0.1:5037         127.0.0.1:61930        TIME_WAIT       0
         TCP    127.0.0.1:5037         127.0.0.1:61931        TIME_WAIT       0
    

    说明当前端口被pid为16636的进程占用。

    1. 运行tasklist 查看列表pid为16636的进程,结果如下:
      java.exe 15476 Console 1 618,368 K
      audiodg.exe 6384 Services 0 13,524 K
      PPAdbServer.exe 16636 Console 1 8,624 K
      360MobileLoader.exe 16552 Console 1 27,852 K
      tangram_cef_renderer.exe 15088 Console 1 40,392 K
      chrome.exe 12552 Console 1 132,308 K
      cmd.exe 1424 Console 1 2,728 K
    2. 找到对应的进程,直接运行taskkill /pid 16636 或者taskkill /im PPAdbServer.exe,或者打开任务管理器关闭对应进程。
      某些流氓软件进程可能关不掉,可以直接卸载。。。
  21. 在android 4.0.3上对sdcard不能创建目录或文件。
    出现场景:
    之前都在android 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/sdcardsdcard的分区……
      /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,所以这里硬编码写死了,如果有的手机还是没办法使用,请自行修改。
  22. Android Gradle Build Error:Some file crunching failed, see logs for details.
    调查原因:
    1.构建Gradle的时候,Gradle会去检查一下是否修改过文件的后缀名;
    2.一般大多数是出现在图片上,.jpg修改成了.png就会出现这个问题;
    3.9patch图片也可能出现这个问题。
    解决方案:

    image.png

  23. 从Git上下载项目时报错如下:

     Error:Failed to open zip file.
     Gradle's dependency cache may be corrupt (this sometimes occurs 
     after a network connection timeout.)
     Re-download dependencies and sync project (requires network)
     Re-download dependencies and sync project (requires network)
    

    调查原因:
    问题出在gradle-wrapper.properties上。我这里配置的是distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip项目中使用了gradle-4.1-all.zip,而gradle-4.1-all.zip无法直接在studio中下载下来。
    解决方案:

    1. 直接去http://services.gradle.org/distributions/找到gradle-4.1-all.zip下载下来,放在类似下面的目录中C:\Users\Administrator.gradle\wrapper\dists\gradle-4.1-all\目录下。
    2. 直接把distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip中的https改成http,重新编译。
  24. 关于报错Error:Configuration with name 'default' not found.的问题
    调查原因:
    gradle的配置出现错误。
    解决方案:

    1. 查看setting.gradle文件中的include 'library' 是否存在本地没有的library,如果存在则删除。
      如:include ':app', ':imagechoose', ':largerimagescaleview',项目中不存在largerimagescaleview,
      删除后为include ':app', ':imagechoose'.
    2. 将app.gradle文件中的compile project(':largerimagescaleview')删除.
    3. 再次编译即可。
  25. navicat 系列软件一点击菜单栏就闪退。
    Windows 10装的是navicat Premium,只要点菜单栏就立马闪退。 于是删了重装,来回装几遍依然如此。后来发现,原来是启动了有道词典屏幕取词才会出现这种现象,关了有道就OK了。

  26. 为什么 Android 不允许子线程中访问 UI 呢?
    因为 Android 的 UI 控件并不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态,那还有一个问题,为什么系统不对 UI 控件的访问加上锁机制呢?缺点有两个:

    • 加上锁机制会让 UI 访问的逻辑变得复杂。
    • 锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。
  27. 调用adapter.notifyDataSetChanged()方法刷新列表时不起作用
    分析方案:

    1. 首先先检查数据是否改变了,在数据源头debug ,
      • 1.1 确定List<object> lists中的这个list有没有新new过,不能新new,要用notifyDataSetChanged()的话,更新数据一定要保持与之前的setAdapter(,lists)中的lists是同一个对象。
      • 1.2 lists的更新数据不能直接赋值,比如不能用lists=lists2,要用 lists.addAll(lists2)。

    2.在数据正确的情况下,然后在检查,notifyDataSetChanged()的执行位置是否在数据改变完之前?
    3.更新数据的是否在UI线程中,如果不在把adapter.notifyDataSetChanged()放在UI线程中 。

  28. Android studio中使用adb连接设备时报错unable to connect to 172.16.0.26:5555: cannot connect to 172.16.0.26:5555: 由于目标计算机积极拒绝,无法连接。 (10061)
    解决方案:
    1、第一步:Android设备开启USB调试,并且通过USB线连接到电脑。
    2、第二步:在终端执行以下命令”adb tcpip 5555“。(重启端口)
    3、第三步:在终端执行以下命令”adb connect 172.16.0.26“(172.16.0.26为Android设备的IP地址)。此时拔出USB线,应该就可以adb通过wifi调试Android设备。

  29. Android WebView clearHistory 失效的解决方案
    应用场景:
    在当前页面loadUrl(A),然后为了复用WebView,直接loadUrl(B),此时点击返回键会返回到A页面(想实现效果是不到A页面,直接退出),这就使用了WebView的clearHistory()方法,不料WebView的clearHistory()方法并未起作用。
    调查原因:
    原因是WebView的clearHistory()有个奇怪的特性,那就是只清除当前页之前的历史记录。假设当前页面为A,我调用WebView的clearHistory()然后loadUrl(B),接着回退还是会退到A。所以正确的调用时机是在B完全载入之后才行。
    解决方案:
    在webview中使用WebViewClient类来控制URL加载,其中有个方法onPageFinished(WebView view, String url),可在该方法中添加WebView的clearHistory()方法。代码如下:

    mWebView.loadUrl("B")        
    mWebView.setWebViewClient(new WebViewClient() {
      @Override
      public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
          handler.proceed();
      }
    
      @Override
      public boolean shouldOverrideUrlLoading(WebView view, String url) {
          view.loadUrl(url);
          return true;
      }
    
      @Override
      public void onPageFinished(WebView view, String url) {
          mWebView.clearHistory();
          super.onPageFinished(view, url);
      }
    
      @Override
      public void onPageStarted(WebView view, String url, Bitmap favicon) {
          super.onPageStarted(view, url, favicon);
      }
      });
    
  30. Glide 加载https的图片.
    一、首先在moudle 的build.gradle中添加依赖。

    compile 'com.squareup.okhttp3:okhttp:3.3.1'
    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
    

    二、新建工具类HttpsUtils.

    public class HttpsUtils {
    
    /**
     * 获取OkHttpClient
     * 设置允许HTTPS
     * */
    public static OkHttpClient getOkHttpClient(InputStream... certificates)
    {
        SSLSocketFactory sslSocketFactory = HttpsUtils.getSslSocketFactory(certificates, null, null);
        OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
        builder = builder.sslSocketFactory(sslSocketFactory);
        builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session)
            {
                return true;
            }
        });
        return builder.build();
    }
    
    public static SSLSocketFactory getSslSocketFactory(InputStream[] certificates, InputStream bksFile, String password){
        try{
            TrustManager[] trustManagers = prepareTrustManager(certificates);
            KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManager trustManager = null;
            if (trustManagers != null){
                trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
            } else{
                trustManager = new UnSafeTrustManager();
            }
            sslContext.init(keyManagers, new TrustManager[]{trustManager}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e){
            throw new AssertionError(e);
        } catch (KeyManagementException e){
            throw new AssertionError(e);
        } catch (KeyStoreException e){
            throw new AssertionError(e);
        }
    }
    
    private class UnSafeHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session){
            return true;
        }
    }
    
    private static class UnSafeTrustManager implements X509TrustManager{
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)throws CertificateException{}
    
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)throws CertificateException{}
    
        @Override
        public X509Certificate[] getAcceptedIssuers(){
            return new X509Certificate[]{};
        }
    }
    
    private static TrustManager[] prepareTrustManager(InputStream... certificates){
        if (certificates == null || certificates.length <= 0) return null;
        try{
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates){
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try{
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e){
                }
            }
            TrustManagerFactory trustManagerFactory = null;
            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            return trustManagers;
        } catch (NoSuchAlgorithmException e){
            e.printStackTrace();
        } catch (CertificateException e){
            e.printStackTrace();
        } catch (KeyStoreException e){
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    
    }
    
    private static KeyManager[] prepareKeyManager(InputStream bksFile, String password){
        try{
            if (bksFile == null || password == null) return null;
            KeyStore clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bksFile, password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());
            return keyManagerFactory.getKeyManagers();
        } catch (KeyStoreException e){
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e){
            e.printStackTrace();
        } catch (UnrecoverableKeyException e){
            e.printStackTrace();
        } catch (CertificateException e){
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    
    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers){
        for (TrustManager trustManager : trustManagers){
            if (trustManager instanceof X509TrustManager){
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }
    
    private static class MyTrustManager implements X509TrustManager{
        private X509TrustManager defaultTrustManager;
        private X509TrustManager localTrustManager;
    
        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            var4.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException{}
    
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException{
            try{
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce){
                localTrustManager.checkServerTrusted(chain, authType);
            }
        }
    
        @Override
        public X509Certificate[] getAcceptedIssuers(){
            return new X509Certificate[0];
        }
    }
    }
    

    三、在application中初始化即可。

    Glide.get(this).register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(HttpsUtils.getOkHttpClient()));
    

    此时glide即可完成对https的支持Glide.with(this).load("https://").into(img);


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,042评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,327评论 0 17
  • 哎呀呀 ,马上就要面临找工作了,媛媛心里紧张呀. 作为一个即将毕业的Android程序媛,开始面临找工作了,...
    仇诺伊阅读 4,505评论 7 59
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • 在迷茫中迷茫,在幻想中幻想。 明明生活中想了解自己却总是不能了解; 明明很纠结却又总是让人觉得很爽快; 明明觉得自...
    葵沫阅读 182评论 0 1