Android开发中遇到的问题及小知识总结

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_30993595/article/details/81675694

前言

写这篇文章主要是为了方便查看在开发中出现的一些问题和解决方法,因为目前平时开发中出现的错误都是记录在笔记本上,一个是不易于保存,而是不易于查看,所以就有了写到这里的想法,这样随时随地都能看到;这篇文章将会不定期更新

APP首次安装

这个问题我感觉大家应该都会碰到:
当应用安装完成,通常会有一个完成提示界面,下方有两个按钮:完成、打开,如图
在这里插入图片描述
实际测试过程中发现现在很多高版本手机在应用市场下载应用安装后不会弹出安装界面,有点类似于静默安装的效果;但是很多6.0及以下的手机会出现安装界面及完成界面

重点是这里有两个按钮,如果你点击完成,手机会退出到桌面,这种情况下一切正常;但是你如果点击打开按钮,就会进入应用界面;比方说进入第一个ActivityA,然后打开第二ActivityB,你以为还是一切正常,但是当你按home键回到桌面,再点击图标进入应用,你以为看到的是ActivityB,但实际上居然是ActivityA;重复这一步骤,你会发现打开的永远是ActivityA;同时当你按返回键,会发现返回到ActivityB,再返回就到了ActivityA,感觉是系统重复的实例化ActivityA

下载了应用市场的一些应用也发现了这种现象,我觉得这是Android系统的一个bug,既然你提供了这两个按钮,那不应该出现这种情况;主要原因是从安装界面打开App和从桌面打开App的任务栈不一样,大家可以打印Intent信息看看,解决办法如下:
在应用第一个Activity的onCreate方法添加如下代码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(!this.isTaskRoot()) {
            Intent intent = getIntent();
            if(intent !=null) {
                String action = intent.getAction();
                if(intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) {
                    finish();
                    return;
                }
            }
        }
        setContentView(R.layout.activity_main);

    }

isTaskRoot 是系统api,作用是判断Activity是不是任务栈的源Activity(上面也说了,home键退出重新进入,又打开了第一个ActivityA,显然不是源Activity,它处在另一个任务栈;如果从正常启动,它就是源Activity),不是就是说被系统重新实例化出来了,此时再判断是否是主界面,如果是主界面就销毁当前的任务栈,让系统调出已经存在的任务栈,即你按home键退出时的界面所在 的任务栈

WebView中图片等资源加载失败

有些情况下WebView中加载一些资源会失败,其中一个原因就是在Android 5.0开始WebView默认不允许加载http与https混合页面,即不支持同时加载Https和Http请求,比如https页面加载http图片或者http页面加载https图片,那图片就显示不出来;这时可以通过如下方法进行设置

//https与http混合资源加载
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}

该方法设置WebView的加载模式,参数有三种选择:

  • MIXED_CONTENT_NEVER_ALLOW:Webview不允许一个安全的站点(https)去加载非安全的站点内容(http),比如https网页内容中的图片是http链接
  • MIXED_CONTENT_ALWAYS_ALLOW:WebView是可以在一个安全的站点(Https)里加载非安全的站点内容(Http), 这是WebView最不安全的操作模式,但是一般是一了百了的写法
  • MIXED_CONTENT_COMPATIBILITY_MODE:当涉及到混合内容时,WebView会尝试去兼容最新Web浏览器的风格。一些不安全的内容(Http)能被加载到一个安全的站点上(Https),而其他类型的内容将会被阻塞。这些内容的类型是被允许加载还是被阻塞,可能会随着版本的不同而改变,官网并没有给出明确的定义。这种模式主要用于在App里面不能控制内容的渲染,但是又希望在一个安全的环境下运行

PhotoView+ViewPager 发生java.lang.IllegalArgumentException: pointerIndex out of range异常

描述: 当PhotoView 和 ViewPager 组合时 ,用双指进行放大时 是没有问题的,但是用双指进行缩小的时候,程序就会崩掉

原因: 多次触发触摸屏事件,导致对同一个事件处理的消息过多,当第一个消息已处理完事件并销毁事件时,
由于该事件已销毁但还没来得及通知销毁第二个同样的消息,当主线程执行第二个消息时,由于获取不了该事件,
所以抛出异常。简单理解就是2个控件对同一个MotionEvent出现了冲突.

解决: 自定义一个类去继承ViewPager ,然后重写Viewpager的 onInterceptTouchEvent()的方法,捕捉该异常。

/**
 * Created by cxy
 * on 2017/7/5.
 * 解决  photoview 与viewpager 组合时 图片缩放的错误 ;
 * 异常:.IllegalArgumentException: pointerIndex out of range
 */

public class PhotoViewPager extends ViewPager{

    public PhotoViewPager(Context context) {
        super(context);
    }

    public PhotoViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        try {
            return super.onInterceptTouchEvent(ev);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        return false;
    }

}

viewpageradapter 调用notifyDataSetChanged()时不刷新页面.

原因: 在getItemPosition()方法的源码中如果item的位置如果没有发生变化,则返回POSITION_UNCHANGED。
如果返回了POSITION_NONE,表示该位置的item已经不存在了。默认的实现是假设item的位置永远不会发生变化,
而返回POSITION_UNCHANGED。

解决: 修改适配器的写法,覆盖getItemPosition()方法,当调用notifyDataSetChanged时,
让getItemPosition方法人为的返回POSITION_NONE,从而达到强迫viewpager重绘所有item的目的。


Java.lang.IllegalStateException: No activity

当使用Fragment去嵌套另外一些子Fragment的时候,我们需要去管理子Fragment,这时候需要调用ChildFragmentManager去管理这些子Fragment,由此可能产生的Exception主要是: Java.lang.IllegalStateException: No activity

描述:当第一次从一个Activity启动Fragment,然后再去启动子Fragment的时候,存在指向Activity的变量,但当退出这些Fragment之后回到Activity,然后再进入Fragment的时候,这个变量变成null

原因:Fragment在detached之后都会被reset掉,但是它并没有对ChildFragmentManager做reset,所以会造成ChildFragmentManager的状态错误

解决:我们需要在Fragment被detached的时候去重置ChildFragmentManager

@Override
  public void onDetach() {
    super.onDetach();
    try {
      Field childFragmentManager = Fragment.class
          .getDeclaredField("mChildFragmentManager");
      childFragmentManager.setAccessible(true);
      childFragmentManager.set(this, null);

    } catch (NoSuchFieldException e) {
      throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }

使用Write_setting权限出现java.lang.IllegalArgumentException: You cannot keep your settings in the secure settings.

在android 6.0及以后,WRITE_SETTINGS权限的保护等级已经由原来的dangerous升级为signature,这意味着我们的APP需要用系统签名或者成为系统预装软件才能够申请此权限,想要获取该权限需要提示用户跳转到修改系统的设置界面去授予此权限,同时debug模式是不能申请该权限

所以你在申请该权限时不能使用checkSelfPermissions检测WRITE_SETTINGS权限,因为它是系统权限,该方法永远返回的是false,而是通过Settings.System.canWrite(Context)检测是否拥有写入系统权限的权限,如果没有就需要引导用户去设置界面进行授权

if (!Settings.System.canWrite(MainActivity.this)) {
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
     builder.setTitle(R.string.notifyTitle);
     builder.setMessage("我们的应用需要您授权\"修改系统设置\"的权限,请点击\"设置\"确认开启");

     builder.setNegativeButton(R.string.cancel,
             new DialogInterface.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
                     finish();
                 }
             });

     builder.setPositiveButton(R.string.setting,
             new DialogInterface.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
                     Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
                             Uri.parse("package:" + getPackageName()));
                     startActivityForResult(intent, ForWrittingSetting);
                 }
             });

     builder.setCancelable(false);

     builder.show();
 }

didn’t find class on path dexpathlist

保证工程和引用库文件用同一个支持包,删除build目录,clean工程
eq:第一个activity引用android.app.activity包,第二个activity引用v7包的AppcompantActivity,这样可能就会报找不到第一个activity


AndroidStudio打包时报错The same input jar is specified twice

原因 是build.gradle文件配置了
dependencies {
compile fileTree(include: '.jar’, dir: ‘libs’)
}
这里面已经添加过jar包,但是又在混淆文件proguard.cfg里面又加了句-libraryjars libs/
**.jar,
将-libraryjars libs/***.jar 前面用#号注释或者直接删掉即可。

注意:sdk 通过 proguard 混淆代码时默认已经将 lib目录中的 jar 都已经添加到打包脚本中,所以不需要再次手动添加。


Activity当中的EditText的软键盘弹出问题

这个在mainfest.xml中activity下面有一个属性为windowSoftInputMode,它共有10个值可以配置,每个配置都对页面输入法弹出框影响不同

  1. stateUnspecified :即未指定状态,当没有设置android:windowSoftInputMode属性的时候,默认是这样的交互方式,系统会依据界面採取对应的软键盘的显示模式。比如当界面上仅仅有文本和button的时候,软键盘就不会自己主动弹出。由于没有输入的必要
  2. stateUnchanged:即状态不改变。就是当前界面的软键盘状态,取决于上一个界面的软键盘状态。比如当前界面键盘是隐藏的,那么跳转之后的界面,软键盘也是隐藏的;当前界面是显示的,那么跳转之后的界面,软键盘也是显示状态
  3. stateHidden:假设我们设置了这个属性,那么键盘状态一定是隐藏的,无论上个界面什么状态。也无论当前界面有没有输入的需求,反正就是不显示。因此,我们能够设置这个属性,来控制软键盘不自己主动的弹出
  4. stateAlwaysHidden:这个属性也能够让软键盘隐藏。
  5. stateVisible:能够将软键盘自动弹出来,即使在界面上没有输入框的情况下也能够强制弹出来
  6. stateAlwaysVisible:也是可以主动将软键盘弹出来,可是与stateVisible属性有小小的不同之处。举个样例,当我们给A界面设置为stateVisible属性,假设当前的界面键盘是显示的,当我们跳转到下个界面再回到当前界面的时候,键盘这个时候是隐藏的;可是假设我们设置为stateAlwaysVisible,我们跳转到下个界面再次回来的时候。软键盘是会显示出来的
  7. adjustUnspecified:设置软键盘与软件的显示内容之间的显示关系。当没有设置这个值的时候,这个选项也是默认的设置模式,第一:页面没有滚动控件的时候,当输入框会被弹出软键盘挡住的情况下,将输入框上移,以保证输入框处于用户视野范围,其它视图不变,如果不会被挡住,页面视图位置不会变动;第二:页面有滚动控件的时候,将页面整体视图上移,保证处于软键盘上方
  8. adjustResize:同上,但是有两点不同,第一:这个属性是将Activity整个界面往上移动,保证处于软键盘的上方,全部View可见;第二:会自动将软键盘弹出
  9. adjustPan:与adjustUnspecified属性类似,但是有一点不同,就是页面有滚动控件的时候,只会保证输入控件不被软键盘挡住,其它视图不保证
  10. adjustNothing:这个属性没有实际意义,软键盘弹出的时候,页面所有View不会变动

socket读取的时候报java.net.SocketException: Connection reset

这是因为
1,如果一端的Socket被关闭(或主动关闭,或因为异常退出而 引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。
2,一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。
简单的说就是在连接断开后的读和写操作引起的。


socket连接的时候报java.net.ConnectException: Connection refused: connect

这是因为服务端没有启动,或者与服务端连接的端口或者ip不一致,意思就是连接不到相对应的服务端


查询服务器端口是否有数据发出

LINUX:
telnet 10.100.20.137 7780 #查看10.100.20.137服务器的7780是否有数据发出
tcpdump udp port 11011 #是否有数据走UDP协议往端口11011发
tcpdump tcp port 8888 #是否有数据走TCP协议往端口8888发

WIN方法:
winshark


端口占用情况

有时候在运行一些开发工具的时候经常会碰到端口被占用的情况,比如adb运行端口被其它流氓软件占用,解决如下

  • 可以通过 netstat -ano,列出所有端口的情况
  • 通过netstat -aon|findstr “5037” (5037是adb端口)命令查看这个端口所对应的进程PID
  • 输入tasklist|findstr “2720” 查看该pid对应进程的进程名称(2720是进程pid)
  • 输入taskkill /im sjk_daemon.exe /f 杀死进程名为sjk_daemon.exe的进程

字符串拼接

在开发中通常使用 + 号来将几个String进行拼接显示到XML中的TextView标签里,这显然是不合理的,可以使用getString方法进行更完美的实现

说到getString方法需要先说到三个约束符

  • %n$ms:n表示第几个参数;m表示间隔几个空格显示,3表示一个空格,4表示两个空格,以此类推;s表示这是一个String的约束符
  • %n$md:前面几个符号与上面类似,只有d不同,表示这是整数的约束符
  • %n$mf:因为f表明这是一个浮点型数据的约束符,所以m表示保留几位小数;.1表明保留一位小数,.2保留两位小数,以此类推,数字前面的点号不要忘了

举例:

eq1:在strings.xml中定义一个格式约束符

<resources>
	<string name="format">%1$s(%2$d/%3$d),%4$s(%5$1.1f)</string>
</resources>

可以看到这里总共是由2个String、2个整数、1个浮点数组成
接下来在代码里使用

String result = getString(R.string.format,"小明作业完成情况",8,16,"完成率",0.5);

打印出来的结果如下

小明作业完成情况(8/16),完成率(0.5)

eq2:上面的格式约束符是由纯符号组成,也可以文字加符号一起

<resources>
	<string name="age">我今年%1$d岁了,来自%2$s</string>
</resources>

这里需要使用format方法,其实你看getString源码实现,就知道最后也是调用的format方法

String age = String.format(getString(R.string.age),15,"北京");

打印结果

我今年15岁了,来自北京

eq3:如果需要文字的一部分用特殊颜色显示,可以使用html

<string name="pay"><![CDATA[<b>应付:</b> <font color=#F09B02>¥%1$.2f</font>]]></string>
<string name="seller"><![CDATA[共<font color=#f37214>%1$d</font>件,合计: <font color=#f37214>¥%2$.2f</font> 元]]> </string>
mText.setText(Html.fromHtml(String.format(getResources().getString(R.string.pay),66.66)));

这里面的应付两个字也可以用上面的例子中的约束符替代,然后在代码里可以动态显示xxx应付,这样就更加动态化了,适应性更强

URL转码

关于url下载链接中包含中文导致下载失败的问题处理方法

先用URLEncoder将整个url进行转码,但是转码后还需要将url中的 / ,: ,空格等再转回来

downUrl = URLEncoder.encode(downUrl, "utf-8");
downUrl = downUrl.replace("\\+", "%20");
downUrl = downUrl.replace("%2F", "/");
downUrl = downUrl.replace("%3A", ":");

获取手机信息

平时开发中可能需要获取手机相关信息,比如IMEI,IMSI号,手机号等

  • IMEI(International Mobile Equipment Identity,国际移动身份识别码):是由15位数字组成的”电子串号”,其组成结构为TAC(6位数字)+FAC(两位数字)+SNR(6位数字)+SP (1位数字)。它与每台手机一一对应,而且该码是全世界唯一的。每一只手机在组装完成后都将被赋予一个全球唯一的一组号码,这个号码从生产到交付使用都将被制造生产的厂商所记录。 IMEI码贴在手机背面的标志上,并且读写于手机内存中。它也是该手机在厂家的”档案”和”身份证号”
  • IMSI(International Mobile Subscriber Identification Number,国际移动用户识别码):是区别移动用户的标志,储存在SIM卡中。可用于区别移动用户的有效信息;IMSI由MCC、MNC、MSIN组成。其中MCC为移动国家号码,由3位数字组成, 唯一地识别移动客户所属的国家,我国为460;MNC为网络id,由2位数字组成,用于识别移动客户所归属的移动网络,中国移动为00,中国联通为01,中国电信为03;MSIN为移动客户识别码,采用等长11位数字构成,唯一地识别国内GSM移动通信网中移动客户
String imei = "";
String imsi = "";
String phoneNum = "";
		
TelephonyManager manager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
imei = manager.getDeviceId();
imsi = manager.getSubscriberId();
phoneNum = manager.getLine1Number();

记得加上权限

< uses-permission android:name=“android.permission.READ_PHONE_STATE”/>


Sqlite查询错误

Couldn't read row 0, col -1 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.

这种问题是由下面这句话导致

cursor.getColumnIndex("xxx"))

这个xxx是表里面的一个字段,报这个错是因为表里没有这个字段,然后通过cursor查询就找不到

展开阅读全文

没有更多推荐了,返回首页