Android开发中小问题汇总二

作者: 闲庭 | 来源:发表于2017-08-04 21:08 被阅读133次

    本来想一直在同一篇文章中不断更新,可发现那样一章篇幅太大,所以拆开记录,虽然这样零散了,可能不太好查找,但是我会附上对应的链接。

    以下问题紧接着Android开发中小问题汇总一

    1. 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优化(设置成关闭)

    2. 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的存在,否则就会崩溃,起到了避免依赖重复资源的作用。

    3. 关于在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;
        }
    
    1. Android BottomSheetDialog设置背景透明问题的解决办法
      出现场景:
      在使用BottomSheetDialog实现底部弹出框时,需要在自定义布局中设置上半部分控件一半为透明,可是怎么在布局中设置都无效,总是没办法显示透明。
      解决办法:
      对BottomSheetDialog设置如下属性mBottomSheetDialog.getWindow().findViewById(R.id.design_bottom_sheet) .setBackgroundResource(R.color.transparent_color);

    2. 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");

    1. 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);
      }
      });
      
    2. 在对原有项目重构时,进行代码迁移,涉及到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还报错的话,建议继续降低版本尝试。

    3. 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,或者打开任务管理器关闭对应进程。
        某些流氓软件进程可能关不掉,可以直接卸载。。。
    4. 在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,所以这里硬编码写死了,如果有的手机还是没办法使用,请自行修改。

    相关文章

      网友评论

        本文标题:Android开发中小问题汇总二

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