Android的布局XML中app:srcCompat和android:src之间的区别

image.png
image.png

image.png

这里用tools:srcCompat不能显示矢量图。

遇到的问题

2020-01-31 13:59:14.564 13074-13074/com.example.note E/SpiderMan: java.lang.NullPointerException: Attempt to invoke virtual method ‘android.os.IBinder android.view.View.getWindowToken()’ on a null object reference at com.example.note.NoteNewActivity.closeSoftKeyInput(NoteNewActivity.java:224)

2020-01-31 14:25:00.270 14531-14538/? E/om.example.not: Failed to send jdwp-handshake response.: Broken pipe
2020-01-31 14:25:00.694 14531-14531/? E/SpiderMan: java.lang.NullPointerException: Attempt to invoke interface method ‘int java.lang.CharSequence.length()’ on a null object reference
at java.util.regex.Matcher.reset(Matcher.java:1059)
image.png

sqlite无法查看表内容


缺少“;”
select * from word;

在CoordinatorLayout的时候,上面的布局挡住下面的布局

在CoordinatorLayout的时候,上面的布局挡住下面的布局,只需要在下面的布局当中加入这个属性即可:

app:layout_behavior=”@string/appbar_scrolling_view_behavior”

Android EditText输入光标居于开头最开始位置

如果欲使EditText加载后的输入光标自动处于最开始处,可以通过设置EditText的android:gravity实现,设置android:gravity为left或者start即可,可以设置:

android:gravity=”start”

或者:

android:gravity=”left”

Android-获取屏幕宽高

Android-删除部分ImageSpan时删除整个ImageSpan?

Android Editable与String的区别

转载一颗西瓜 最后发布于2017-12-29 13:11:51 阅读数 232 收藏
展开
Editable 是一个接口类型,对它的实例化对象作出任何改变都是对原有的实例化对象操作的,内存地址还是原来的那个。
而对 String 的任何改变都是相当于重新实例化了一个 String 类出来,相当于重新分配了内存地址。
所以说 Editable 是可变的,String 是不可变的了;因为 Editable 变了之后还是原来的 Editable 对象,String 变了之后就已经不是原来的 String 对象了。

getText()和getEditableText()使用比较

(1)getText()和getEditableText()这两个方法是定义在android.widget.TextView控件中的,在默认情况下TextView是不可编辑的,getText().toString()能获得控件中的内容,而getEditableText()返回的值为null。

(2)android.widget.EditText继承自TextView,它只稍微重写了TextView中的getText()方法,。在EditText中使用getText().toString() 和getEditableText().toString()效果是一样的

详解android:scaleType属性

转载encienqi 最后发布于2012-08-27 18:03:11 阅读数 95723 收藏

android:scaleType是控制图片如何resized/moved来匹对ImageView的size。
ImageView.ScaleType / android:scaleType值的意义区别:
CENTER /center 按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示
CENTER_CROP / centerCrop 按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽)
CENTER_INSIDE / centerInside 将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽
FIT_CENTER / fitCenter 把图片按比例扩大/缩小到View的宽度,居中显示
FIT_END / fitEnd 把图片按比例扩大/缩小到View的宽度,显示在View的下部分位置
FIT_START / fitStart 把图片按比例扩大/缩小到View的宽度,显示在View的上部分位置
FIT_XY / fitXY 把图片不按比例扩大/缩小到View的大小显示
MATRIX / matrix 用矩阵来绘制,动态缩小放大图片来显示。
** 要注意一点,Drawable文件夹里面的图片命名是不能大写的


android:scaleType=”centerCrop”

以填满整个ImageView为目的,将原图的中心对准ImageView的中心,等比例放大原图,直到填满ImageView为止(指的是ImageView的宽和高都要填满),原图超过ImageView的部分作裁剪处理。

android:scaleType=”fitXY”

把原图按照指定的大小在View中显示,拉伸显示图片,不保持原比例,填满ImageView.

编译’com.zhihu.android:matisse:$latest_version’

$latest_version要替换成具体版本号

Android获取不到Dialog自定义布局中的控件

想要获取自定义布局中的控件必须调用你所定义的View的findViewById方法,而不能像获得其他控件一样直接调用findViewById方法。。。。”

  1. final View layout = inflater.inflate(R.layout.send_email_dialog,
  2. (ViewGroup) findViewById(R.id.send_email_dialog_ll));
  3. final EditText sendEmailCon_ET = (EditText) layout.findViewById(R.id.send_email_dialog_et);

getExternalFilesDir(Environment.DIRECTORY_RINGTONES)

返回的字段后面不包括斜杆,如果要拼接路径,需要手动加上。

  1. mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH,context.getExternalFilesDir(Environment.DIRECTORY_RINGTONES) + "/" + fileName);

TextView和clickablespan和scrollview

setMovementMethod(LinkMovementMethod.getInstance()),让超链接起作用。顺便提下,TextView的setMovementMethod()和setIsSelectable()方法都可以让TextView支持内容滚动。

Android TextView设置ClickableSpan 点击结尾空白位置也响应点击的问题

  1. 解决方法有两个:1:给textview的宽设置warp_content
  1. 2:如果需要使用0dpweight的形式,可以给textview设置text 的时候在末尾加一个空格\u3000字符

1 TextView设置了ClickableSpan,TextView宽度为match_parent时,会产生如下问题:
第一个问题如果设置的Span文本没有填满match_parent,此时点击空白处也会执行clickableSpan的click函数;
第二个问题是如果添加的文本超过了match_parent,此时点击TextView中没有添加ClickableSpan的文本,click事件会被拦截。

第一个问题如何解决:在SpannableString的结尾添加 spannable.append(“\u200b”);

另外一种办法: 我管理通过延伸LinkMovementMethod来解决它,并检查是否触摸事件 fset等于或大于文本长度:

  1. public class MovementMethod extends LinkMovementMethod {
  2. @Override
  3. public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
  4. int action = event.getAction();
  5. if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
  6. int x = (int) event.getX();
  7. int y = (int) event.getY();
  8. x -= widget.getTotalPaddingLeft();
  9. y -= widget.getTotalPaddingTop();
  10. x += widget.getScrollX();
  11. y += widget.getScrollY();
  12. Layout layout = widget.getLayout();
  13. int line = layout.getLineForVertical(y);
  14. int off = layout.getOffsetForHorizontal(line, x);
  15. if (off >= widget.getText().length()) {
  16. // Return true so click won't be triggered in the leftover empty space
  17. return true;
  18. }
  19. }
  20. return super.onTouchEvent(widget, buffer, event);
  21. }
  22. }

有一点需要注意,因为我需要重写静态getInstance()函数,否则您仍然会返回基础LinkMovementMethod的实例.

第二个问题解决办法:
设置了ClickableSpan需要添加setMovementMethod(LinkMovementMethod.getInstance());才能失败click事件,重写setMovementMethod用到的对象。
TextView富文本学习三-设置了ClickableSpan后与TextView click事件冲突

Android 设置TextView滑动滚动条和滑动效果

1、单独的TextView控件设置滚动条(滑动不流畅

实际上这个自带点击的滑动效果贼垃圾,一碰就触发点击了,根本不滑。容易与clickablespan点击事件冲突。

  1. <TextView
  2. android:id="@+id/content"
  3. android:layout_width="fill_parent"
  4. android:layout_height="wrap_content"
  5. android:text="file content is empty!"
  6. android:scrollbars="vertical"
  7. android:fadeScrollbars="false"/>

在activity中为这个TextView设置:

  1. mFileContentView = (TextView) findViewById(R.id.content);
  2. mFileContentView.setMovementMethod(ScrollingMovementMethod.getInstance());

经过上面两个步骤,TextView就可以上下滚动了,如果想自定义滚动条,接着在xml里面加入属性:

android:scrollbarThumbVertical=”@drawable/ic_launcher” //滑块的图片
android:scrollbarTrackVertical=”@drawable/ic_launcher” //滑道的图片

ScrollBar由两部分组成,一个是Track(滑道),一个是Thumb(滑块)

2、也可以用ScrollView(滑动流畅

  1. <ScrollView
  2. android:layout_width="fill_parent"
  3. android:layout_height="wrap_content"
  4. android:scrollbars="vertical"
  5. android:fadingEdge="vertical">
  6. <TextView
  7. android:id="@+id/content"
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:text="file content is empty!"/>
  11. </ScrollView>

解决ListView里TextView设置LinkMovementMethod后导致其ItemClick失效的问题

上述源码为LinkMovementMethod类的第188行。发现了Item点击失效的原因是因为onTouch方法总是返回true而并没有根据不同的情况来作处理,此时大家可能会想直接对这个方法重写就能解决问题了。我和大家想的也一样,对该方法重写,并根据情况返回true或false


把要处理的代码放在了TextView的onTouch方法里,然后根据不同的情况返回true或false.在getView()方法里对相应的TextView进行设置吧
参考TextView添加ClickableSpan和LinkMovementMethod之间的关系

  1. tv.setOnTouchListener(new View.OnTouchListener() {
  2. @Override
  3. public boolean onTouch(View v, MotionEvent event) {
  4. int action = event.getAction();
  5. TextView tv = (TextView) v;
  6. CharSequence text = tv.getText();
  7. //这里的SpannableString根据实际情况可以更换
  8. if (text instanceof SpannableString) {
  9. if (action == MotionEvent.ACTION_UP) {
  10. int x = (int) event.getX();
  11. int y = (int) event.getY();
  12. x -= tv.getTotalPaddingLeft();
  13. y -= tv.getTotalPaddingTop();
  14. x += tv.getScrollX();
  15. y += tv.getScrollY();
  16. Layout layout = tv.getLayout();
  17. int line = layout.getLineForVertical(y);
  18. int off = layout.getOffsetForHorizontal(line, x);
  19. //这里的SpannableString根据实际情况可以更换
  20. ClickableSpan[] link = ((SpannableString)text).getSpans(off, off, ClickableSpan.class);
  21. if (link.length != 0) {
  22. link[0].onClick(tv);
  23. } else {
  24. //do textview click event
  25. }
  26. }
  27. }
  28. return false;
  29. }
  30. });

Android部分文字点击SpannableString实现及滑动冲突处理

滑动冲突处理——自定义MovementMethod
这里就是复制修改下LinkMovementMethod的源码,把滑动部分去掉就可以了。

  1. public class CustomMovementMethod extends BaseMovementMethod {
  2. private static CustomMovementMethod customMovementMethod;
  3. public static CustomMovementMethod getInstance() {
  4. if (customMovementMethod == null) {
  5. synchronized (CustomMovementMethod .class) {
  6. if (customMovementMethod == null) {
  7. customMovementMethod = new CustomMovementMethod ();
  8. }
  9. }
  10. }
  11. return customMovementMethod;
  12. }
  13. @Override
  14. public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
  15. int action = event.getAction();
  16. if (action == MotionEvent.ACTION_UP ||
  17. action == MotionEvent.ACTION_DOWN) {
  18. int x = (int) event.getX();
  19. int y = (int) event.getY();
  20. x -= widget.getTotalPaddingLeft();
  21. y -= widget.getTotalPaddingTop();
  22. x += widget.getScrollX();
  23. y += widget.getScrollY();
  24. Layout layout = widget.getLayout();
  25. int line = layout.getLineForVertical(y);
  26. int off = layout.getOffsetForHorizontal(line, x);
  27. ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
  28. if (link.length != 0) {
  29. if (action == MotionEvent.ACTION_UP) {
  30. // 只处理点击事件
  31. link[0].onClick(widget);
  32. }
  33. return true;
  34. }
  35. }
  36. return super.onTouchEvent(widget, buffer, event);
  37. }
  38. private CustomMovementMethod () {
  39. }
  40. }

tv.setMovementMethod(CustomMovementMethod.getInstance());是重点,漏了这句点击没有效果。

自定义可点击的ImageSpan并在TextView中内置“View“

解决TextView中ClickableSpan点击事件

点击clickableSpan之外的其他地方是可以触发普通的点击事件,问题是点击clickableSpan区域时,不仅会触发ClickableSpan.onClick(view),还会触发TextView.onClick(view)

解决Android TextView富文本拦截点击事件

添加了长按事件
解决TextView 设置ClickableSpan之后,点击和滑动冲突以及空白区域处理
1:ClickableSpan的点击和TextView的长文本滑动冲突
2:点击TextView空白区域,总选中最后一个文本

安卓 TextView 七宗罪

安卓自带文本控件 TextView 有七个比较恶心人的地方:

  1. 默认情况下,点击 ClickableSpan 的文本时会同时触发绑定在 TextView 的监听事件;
  2. 默认情况下,点击 ClickableSpan 的文本之外的文本时,TextView 会消费该事件,而不会传递给父 View;
  3. 固定 TextView 行数的时候,点击 ClickableSpan 文本会出现滚动现象;
  4. 在 8.0 系统上 Html.from() + ClickableSpan + TextUtils.concat() 点击事件表现异常;
  5. Spannable 与 Ellispsize 同时使用时,Ellipsize 失效;
  6. TextView 性能较低;
  7. 折行策略不尽人意,在小米 2s 上尤其明显;
  8. 诡异的 ClickableSpan 表现,在 TextView 上绑定点击事件,又在某段子串上绑定 ClickableSpan,根据 ClickableSpan#onClick() 内部的代码不同,会只触发 ClickableSpan 或既触发 ClickableSpan 又触发 TextView 绑定的事件;
  9. 明明设置2行,当前3行都是 ImageSpan 实现的 emoji 时,第3行的顶部会被绘制出来;

scrollview

记事本 - 图5
传进监听器的是一个event对象,就是用户的touch动作。得到这个event对象之后,我们调用event的getAction方法对这个动作进行判断。
getAction()有 Action_UP Action_DOWN Action_MOVE等结果,这里只用到了Action_MOVE;
再提几个方法:
1.ScrollView的getScrollY() : 滑动条垂直滑动的距离(就是当前视图相对于屏幕原点在Y轴上的偏移量,首先明确Android坐标概念,(0,0)处于左上角,向右是X轴正方向,向下是Y轴正方向,假如向上滚动显示下文,Y值必须为正数,因为当你向上移动后,原先的屏幕原点已经被甩到上面去了,超出屏幕之外,当前视图中的屏幕左上角相对于屏幕原点(是固定不变的的)已经在下方了,所以是正数)
2.View的getHeight(): 该View在屏幕上显示的的高度(和手机屏幕的高度是有区别的)。在本例中,在屏幕上显示的最外层的View是一个ScrollView,所以就用ScrollView调用这个方法得到屏幕的高度。tips:屏幕的高度是这样计算的 width = activity.getWindowManager().getDefaultDisplay().getWidth();
3.View的 getMeasuredHeight(): View的总高度。 当屏幕可以包裹整个View的时候,它和getHeight相等。比方说这个TextView有1000行,那高度就是1000行的高度。本例中我们测量的是Text的高度,所以就用TextView调用这个方法得到。因为TextView是ScrollView中的第一层子View,所以也可以用ScrollView.getChildAt(0)得到TextView对象,
到达底部后,我们用TextView的append()方法追加一条string, 这里是无限追加的,就是说每次到底部都会追加一条,无穷无尽,呵呵!

作者:陈利健
链接:https://www.jianshu.com/p/6a37c3256c6b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

ScrollView常见问题(不能填满屏幕、内部layout_weight无效、进入自动下滑问题等)

不能填满屏幕、内部layout_weight无效—> android:fillViewport=”true”

Android 将本地资源图片转换成Drawable,进行设置大小

  • 将本地图片(R.drawable.image)变成Drawable对象
  • 将Drawable对象转换成Bitmap对象
  • 将Bitmap对象根据指定大小创建一个新的Bitmap对象
  • 将Bitmap对象转换成Drawable对象

Context.getResources().getDrawable(int res)方法过时

Context.getDrawable(int id, Resources.Theme theme), 第二个参数@theme可以为空值.或Context.getDrawable(int)。
但是这个方法的兼容性不是很好,只有在API Level 21及以上才能使用(也就是Android5.0以上)。最后找到了以下解决方案:
ContextCompat.getDrawable(context,R.drawable.icon_test);
这个方法看着容易理解,同时谷歌也推荐使用这种方法。

BitmapFactory.decodeStream 内存溢出java.lang.OutOfMemoryError

图片过大(5.78M左右)就可能导致内存溢出java.lang.OutOfMemoryError,通常大家的第一反应是在使用图片之前进行压缩(第一:质量压缩,第二:图片按比例大小压缩,第三:图片按比例大小压缩)但是这个思路是错误的。对质量压缩的理解(仅供参考):不像网上所说的质量压缩既不失真有能压缩,亲测会失真,我觉得任何压缩方法都会付出代价,只是方法合适代价会小,或者牺牲次要方面,来维持我们的目的。下面是bitmap的compress方法官方介绍:
网上会对该方法的参数quality曲解 先看官方介绍:
@param quality Hint to the compressor, 0-100. 0 meaning compress for
small size, 100 meaning compress for max quality. Some
formats, like PNG which is lossless, will ignore the
* quality setting
认为100 就是不压缩 ,大小不变,其实是错误的理解 quality只表示compress程度,也就是只要使用compress 就会压缩 ,设置为100 文件也会 变小,当然0表示压缩到最小。经过测试,网上对质量压缩的优化 :

  1. Bitmap bitmap = null;
  2. while (true) {
  3. // 这一步是根据要设置的大小,使宽和高都能满足
  4. if ((options.outWidth >> i <= size)
  5. && (options.outHeight >> i <= size)) {
  6. // 重新取得流,注意:这里一定要再次加载,不能二次使用之前的流!
  7. temp = this.getAssets().open(path);
  8. // 这个参数表示 新生成的图片为原始图片的几分之一。
  9. options.inSampleSize = (int) Math.pow(2.0D, i);
  10. // 这里之前设置为了true,所以要改为false,否则就创建不出图片
  11. options.inJustDecodeBounds = false;
  12. bitmap = BitmapFactory.decodeStream(temp, null, options);
  13. break;
  14. }
  15. i += 1;
  16. }
  17. return bitmap;

没有必要,我试了试不起作用,传到服务器会图片变成全黑的情况 。
重点:正确解决方案 设置inSampleSize
先说原因吧,BitmapFactory.decodeStream 内存溢出java.lang.OutOfMemoryError 是因为
使用Content Provider android.content.ContentResolver.openInputStream 去取数据时,因为图片过大就会报错,而不是bitmap显示时报错。
所以在使用前压缩的思路是错误了。

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inSampleSize = 4;
  3. Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
  4. image1.setImageBitmap(bitmap);

cr:是 android.content.ContentResolver 对象
image1: 是 ImageView对象
网上很多说设Options.inJustDecodeBounds = true; 可 能会出错 ,bitmap 会为null 图片全白 的现象 ,如果你是这种现象,去掉试试。
关于options.inSampleSize 大小设置 网上有优化这里只做简单处理 ,有大牛做动态计算合适大小。自己没尝试过就不做叙述了。
说的不一定对但都是自己亲测的一点感悟,网上很多理解都是错误的,希望可以帮到遇到同样问题的你。

安卓BitmapFactory.decodeStream报java.lang.OutOfMemoryError内存溢出
有效解决Android加载大图片内存溢出的问题
android 通过uri获取bitmap图片并压缩
ImageSpan使用时的坑(绘制出来图片大小不对的问题)
android 关于图片压缩库Luban.load(Uri)的问题

Android库对于加载图片来说不是很聪明,所以你必须为此创建解决方法。
在我的测试中,Drawable.createFromStream比使用更多的内存BitmapFactory.decodeStream
你可以更改颜色方案以减少内存(RGB_565),但图像也会失去质量:

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inPreferredConfig = Config.RGB_565;
  3. Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

参考:http//developer.android.com/reference/android/graphics/Bitmap.Config.html
你还可以加载缩放的图像,这会减少很多内存使用量,但您必须知道图像的质量不会过高:

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inSampleSize = 2;
  3. Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

参考:http : //developer.android.com/reference/android/graphics/BitmapFactory.Options.html
要动态定义inSampleSize,你可能想知道图像大小以作出决定:

  1. BitmapFactory.Options options = new BitmapFactory.Options();
  2. options.inJustDecodeBounds = true;
  3. bitmap = BitmapFactory.decodeStream(stream, null, options);
  4. int imageHeight = options.outHeight;
  5. int imageWidth = options.outWidth;
  6. options.inJustDecodeBounds = false;
  7. // recreate the stream
  8. // make some calculation to define inSampleSize
  9. options.inSampleSize = ?;
  10. Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

您可以根据设备的屏幕大小自定义inSampleSize。要获得屏幕大小,您可以执行以下操作:

  1. DisplayMetrics metrics = new DisplayMetrics();
  2. ((Activity) activity).getWindowManager().getDefaultDisplay().getMetrics(metrics);
  3. int screenWidth = metrics.widthPixels;
  4. int screenHeight =metrics.heightPixels;

官方参考高效加载大型位图

处理输入法可见度

当输入法出现在屏幕上时,它会减少应用界面的可用空间。系统会决定应如何调整界面的可见部分,但可能操作不正确。为了确保应用具有最佳行为,您应该指定希望系统如何在剩余空间中显示界面。
要在 Activity 中声明您的首选处理方式,请使用清单的 <activity> 元素中的 [android:windowSoftInputMode](https://developer.android.com/guide/topics/manifest/activity-element.html#wsoft) 属性,该属性包含其中一个“adjust”值。
例如,要确保系统将布局大小调整为可用空间,以确保所有布局内容均可访问(尽管可能需要滚动),请使用 "adjustResize"

对Android 软键盘向下的监听

对 Edittext.onKeyPreIme() 方法重写
为什么 重写 Activity.onKeyDown() 方法为什么没有用?

Android监听键盘弹出与隐藏事件

其实系统提供的是InputMethodManager,让我们控制键盘的弹出和隐藏,而不是键盘弹出和隐藏触发事件。

参考Android 监听键盘弹出收起

搜到的一些监听键盘的方法必须要windowSoftInputMode属性为adjustResize时才可以用。这个方法不适用与app全屏显示(全屏时布局大小并不会改变)。有一个类可以实现监听键盘弹出收起,不用考虑windowSoftInputMode属性与Activity是否全屏。

  1. package com.example.note.util;
  2. import android.app.Activity;
  3. import android.graphics.Rect;
  4. import android.view.View;
  5. import android.view.ViewTreeObserver;
  6. /**
  7. * Created by liujinhua on 15/10/25.
  8. */
  9. public class SoftKeyBoardListener {
  10. private View rootView; // activity的根视图
  11. int rootViewVisibleHeight; // 纪录根视图的显示高度
  12. private OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener;
  13. public SoftKeyBoardListener(Activity activity) {
  14. //获取activity的根视图
  15. rootView = activity.getWindow().getDecorView();
  16. //监听视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变
  17. rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  18. @Override
  19. public void onGlobalLayout() {
  20. //获取当前根视图在屏幕上显示的大小
  21. Rect r = new Rect();
  22. rootView.getWindowVisibleDisplayFrame(r);
  23. int visibleHeight = r.height();
  24. System.out.println(""+visibleHeight);
  25. if (rootViewVisibleHeight == 0) {
  26. rootViewVisibleHeight = visibleHeight;
  27. return;
  28. }
  29. //根视图显示高度没有变化,可以看作软键盘显示/隐藏状态没有改变
  30. if (rootViewVisibleHeight == visibleHeight) {
  31. return;
  32. }
  33. //根视图显示高度变小超过200,可以看作软键盘显示了
  34. if (rootViewVisibleHeight - visibleHeight > 200) {
  35. if (onSoftKeyBoardChangeListener != null) {
  36. onSoftKeyBoardChangeListener.keyBoardShow(rootViewVisibleHeight - visibleHeight);
  37. }
  38. rootViewVisibleHeight = visibleHeight;
  39. return;
  40. }
  41. //根视图显示高度变大超过200,可以看作软键盘隐藏了
  42. if (visibleHeight - rootViewVisibleHeight > 200) {
  43. if (onSoftKeyBoardChangeListener != null) {
  44. onSoftKeyBoardChangeListener.keyBoardHide(visibleHeight - rootViewVisibleHeight);
  45. }
  46. rootViewVisibleHeight = visibleHeight;
  47. return;
  48. }
  49. }
  50. });
  51. }
  52. private void setOnSoftKeyBoardChangeListener(OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {
  53. this.onSoftKeyBoardChangeListener = onSoftKeyBoardChangeListener;
  54. }
  55. public interface OnSoftKeyBoardChangeListener {
  56. void keyBoardShow(int height);
  57. void keyBoardHide(int height);
  58. }
  59. public static void setListener(Activity activity, OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {
  60. SoftKeyBoardListener softKeyBoardListener = new SoftKeyBoardListener(activity);
  61. softKeyBoardListener.setOnSoftKeyBoardChangeListener(onSoftKeyBoardChangeListener);
  62. }
  63. }

Activity中调用:

  1. SoftKeyBoardListener.setListener(getActivity(), new SoftKeyBoardListener.OnSoftKeyBoardChangeListener() {
  2. @Override
  3. public void keyBoardShow(int height) {
  4. //Toast.makeText(getActivity(), "键盘显示 高度" + height, Toast.LENGTH_SHORT).show();
  5. }
  6. @Override
  7. public void keyBoardHide(int height) {
  8. //Toast.makeText(getActivity(), "键盘隐藏 高度" + height, Toast.LENGTH_SHORT).show();
  9. }
  10. });

Android软键盘弹出和收回监听

软键盘弹出和收回的三种方式:
第一种方案:
自定义Edittext

第二种方案:
软键盘弹出监听(通过高度计算,但是这种方式有问题,不推荐用):

第三种方案:
自定义RelativeLayout

Android中visibility属性VISIBLE、INVISIBLE、GONE的区别

而INVISIBLE和GONE的主要区别是:当控件visibility属性为INVISIBLE时,界面保留了view控件所占有的空间;而控件属性为GONE时,界面则不保留view控件所占有的空间。

androidx.appcompat.view.menu.ActionMenuItemView无法转换为android.view.MenuItem

  1. toolbar2.getMenu().findItem(R.id.action_refresh).setVisible(false);

也可以定义全局变量Menu menu,在onCreateOptionsMenu初始化。

  1. public boolean onCreateOptionsMenu(Menu menu) {
  2. // Inflate the menu; this adds items to the action bar if it is present.
  3. getMenuInflater().inflate(R.menu.main, menu);
  4. final MenuItem item = menu.findItem(R.id.ab_search);
  5. searchView = (SearchView) MenuItemCompat.getActionView(item);
  6. }

动态改变actionbar上menu的图标

你不能在onCreate()的菜单项上使用findViewById(),因为菜单布局尚未充满。你可以创建一个全局的Menu变量并在onCreateOptionsMenu()中初始化它,然后在你的onClick()中使用它。代码:
private Menu menu;
在你的onCreateOptionsMenu():
this.menu = menu;
在你的按钮的onClick()方法中:
menu.getItem(0).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_launcher));
改变最右边的menu图标

  1. MenuItem moreItem;
  2. @Override
  3. public boolean onCreateOptionsMenu(Menu menu) {
  4. moreItem = menu.add(Menu.NONE, Menu.FIRST, Menu.FIRST, null);
  5. moreItem.setIcon(R.drawable.action_bar_icon_more);
  6. moreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
  7. return super.onCreateOptionsMenu(menu);
  8. }

Android应用中Back键的监听及处理

  1. package cn.testnbackpressed;
  2. import android.os.Bundle;
  3. import android.view.KeyEvent;
  4. import android.app.Activity;
  5. /**
  6. * Demo描述:
  7. * 处理Back键按下事件
  8. *
  9. * 注意事项:
  10. * 以下两种方法勿一起使用
  11. */
  12. public class MainActivity extends Activity {
  13. @Override
  14. protected void onCreate(Bundle savedInstanceState) {
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.main);
  17. }
  18. /**
  19. * 监听Back键按下事件,方法1:
  20. * 注意:
  21. * super.onBackPressed()会自动调用finish()方法,关闭
  22. * 当前Activity.
  23. * 若要屏蔽Back键盘,注释该行代码即可
  24. */
  25. @Override
  26. public void onBackPressed() {
  27. super.onBackPressed();
  28. System.out.println("按下了back键 onBackPressed()");
  29. }
  30. /**
  31. * 监听Back键按下事件,方法2:
  32. * 注意:
  33. * 返回值表示:是否能完全处理该事件
  34. * 在此处返回false,所以会继续传播该事件.
  35. * 在具体项目中此处的返回值视情况而定.
  36. */
  37. @Override
  38. public boolean onKeyDown(int keyCode, KeyEvent event) {
  39. if ((keyCode == KeyEvent.KEYCODE_BACK)) {
  40. System.out.println("按下了back键 onKeyDown()");
  41. return false;
  42. }else {
  43. return super.onKeyDown(keyCode, event);
  44. }
  45. }
  46. @Override
  47. protected void onDestroy() {
  48. super.onDestroy();
  49. System.out.println("执行 onDestroy()");
  50. }
  51. }

Android 回退键监听

方法1:回调方法onBackPressed

这个方法可以阻止用户点击后退键来退出程序。
一般的像升级程序或者重要数据传输页面都是要阻止用户轻易退出的。
这个方法也是我们最简单、常用的一个方法。

方法2:回调方法onKeyDown

这个方法是监听按键事件,但是阻止用户的行为,并且这种方法监听不到Home键的事件。

方法3:回调方法dispatchKeyEvent

这是个事件分发的方法,无论手指按下屏幕或滑动屏幕、离开屏幕都是会触发这个方法

  1. @Override
  2. public boolean dispatchKeyEvent(KeyEvent event) {
  3. Log.i(LOG_TAG, "dispatchKeyEvent: keyCode -- " + event.getKeyCode());
  4. if (event.getKeyCode()==4){//回退键的KeyCode是4.
  5. return false;//表示不分发
  6. }else{
  7. return super.dispatchKeyEvent(event);
  8. }
  9. }

如果事件没有分发(返回false),onBackPressed方法和onKeyDown方法都是没有得到回调的


正常情况肯定是用第一种方法咯。

后面两种方法涉及到事件的分发。
事件分发要彻底理解是一个相当麻烦的过程:包括事件分发、事件拦截、事件处理,并且手指按下、移动、松开都要分析三个事件情况。

上面三个方法中,如果dispatchKeyEvent返回false,后面两个方法是无法得到回调的。
如果onKeyDown返回false,onBackPressed方法是没有得到回调的。

android LinearLayout布局嵌套覆盖问题

要设置layout_weight属性

在CoordinatorLayout的时候,上面的布局挡住下面的布局

在CoordinatorLayout的时候,上面的布局挡住下面的布局,只需要在下面的布局当中加入这个属性即可:
app:layout_behavior=”@string/appbar_scrolling_view_behavior”

序列化保存样式

Serializable

ObjectOutputStream既可以写入基本类型,如intboolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object
因为写入**Object**时需要大量的类型信息,所以写入的内容很大。

**反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

  1. import java.io.*;
  2. import java.util.Arrays;
  3. /**
  4. * @authur zhangqizky
  5. * @apiNote 序列化与反序列化,将java对象变成二进制内容,即byte[]数组.
  6. * 可序列化的Java对象必须实现java.io.Serializable接口,类似Serializable这样的空接口被称为“标记接口”(Marker Interface);
  7. 反序列化时不调用构造方法,可设置serialVersionUID作为版本号(非必需);
  8. Java的序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如JSON。
  9. */
  10. public class MySeralize
  11. {
  12. public static void main(String[]args)throws IOException,ClassNotFoundException
  13. {
  14. //序列化
  15. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  16. try(ObjectOutputStream os = new ObjectOutputStream(buffer))
  17. {
  18. os.writeInt(12345);
  19. os.writeUTF("Hello");
  20. os.writeObject(Double.valueOf(123.456));
  21. }
  22. System.out.println(Arrays.toString(buffer.toByteArray()));
  23. ByteArrayInputStream bufferin = new ByteArrayInputStream(buffer.toByteArray());
  24. //反序列化,反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行
  25. try(ObjectInputStream in = new ObjectInputStream(bufferin))
  26. {
  27. int n = in.readInt();
  28. String s = in.readUTF();
  29. Double d = (Double)(in.readObject());
  30. System.out.println(n);
  31. System.out.println(s);
  32. System.out.println(d);
  33. }
  34. }
  35. }

Parcel与Parcelable

TextAppearanceSpan实现了writeToParcel,但是没有创建Creator常量。所以我们可以先将TextAppearanceSpan对象通过writeToParcel保存为Parcel对象。然后将其转变为byte[]保存。TextAppearanceSpan提供了Parcel参数的构造函数。

SpannableStringBuilder实现不了Serializable,里面有一个字段没有实现序列化。
**Android中实现序列化有两个选择:一是实现Serializable接口(是JavaSE本身就支持的),一是实现Parcelable接口(是Android特有功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC))。实现Serializable接口非常简单,声明一下就可以了,而实现Parcelable接口稍微复杂一些,但效率更高,推荐用这种方法提高性能。
注:Android中Intent传递对象有两种方法:一是Bundle.putSerializable(Key,Object),另一种是Bundle.putParcelable(Key,Object)。当然这些Object是有一定的条件的,前者是实现了Serializable接口,而后者是实现了Parcelable接口。

实现parcelable接口中list处理方法
适用于非自定义对象
dest.writeList(sub_type);

source.readList(parcelableHelperPo.sub_type, getClass().getClassLoader());

自定义集合 Book

//自定义集合 Book实现了Parcelable接口 private ArrayList books;

out.writeTypedList(books);

// 用这种方法会报错。
// books=p.readArrayList(Thread.currentThread().getContextClassLoader());

  1. // 传递自定义List 只能用以下方法,p为Parcel<br /> books= p.createTypedArrayList(Book.CREATOR);

官方例子

  1. public class MyParcelable implements Parcelable {
  2. private int mData;
  3. public int describeContents() {
  4. return 0;
  5. }
  6. public void writeToParcel(Parcel out, int flags) {
  7. out.writeInt(mData);
  8. }
  9. public static final Parcelable.Creator<MyParcelable> CREATOR
  10. = new Parcelable.Creator<MyParcelable>() {
  11. public MyParcelable createFromParcel(Parcel in) {
  12. return new MyParcelable(in);
  13. }
  14. public MyParcelable[] newArray(int size) {
  15. return new MyParcelable[size];
  16. }
  17. };
  18. private MyParcelable(Parcel in) {
  19. mData = in.readInt();
  20. }
  21. }

Parcel类

Parcel主要用于在进程间通讯,传递数据。
Parcel通过obtain()静态方法获取,数据的存储和读取主要通过writeXXX()readXXX()实现,marshall()unmarshall()将数据序列化和反序列化,最后recycle()回收资源。

读取时记得设置setDataPosition(0)

serialization parcable
文件操作,且用到了反射 单独的内存空间,速度快
会创造大量的读写对象 直接操作内存读写
实现简单 实现复杂,而且读和取的数据要一致
写入的时候,会有字段名,长度等 只是写入数据,节省资源
因为写在文件中,适合持久化数据 不适合持久化数据,可能会变化

作者:Wellijohn
链接:https://juejin.im/post/5a3b24ab6fb9a04515440bd7
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


选择序列化方法的原则
1)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
2)Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
3)Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

ANDROID:SERIALIZABLE和PARCELABLE的持久化保存

前言:
Serializable和Parcelable能够将对象序列化,使之能够在一些特殊场景中进行传输,再进行数据的恢复(Serializable是Java实现的接口,而Parcelable是Android实现的)。
两者作用类似但是实现等其他方面有较大区别,比较多的说法认为Parcelable不适合用于数据的持久化保存,但合适与否和是否可行完全是两码事(GSON是另外一种保存对象的方式)。
出于好奇想从性能方面比较两者持久化的异同。

一.Serializable

需要实现的类如下

  1. class SerialData implements Serializable {
  2. private static final long serialVersionUID = -7060210544622464481L;
  3. public ArrayList<String> array;
  4. public SerialData() {
  5. array = new ArrayList<String>();
  6. for (int i = 0; i < 100000; i++) {
  7. array.add(new String("123"));
  8. }
  9. }
  10. }

写方法

  1. private void saveAsSerializable(SerialData data) {
  2. FileOutputStream fos;
  3. ObjectOutputStream oos;
  4. try {
  5. fos = getApplicationContext().openFileOutput(TAG,
  6. Context.MODE_PRIVATE);
  7. oos = new ObjectOutputStream(fos);
  8. oos.writeObject(data);
  9. oos.close();
  10. fos.close();
  11. } catch (Exception e) {
  12. // TODO Auto-generated catch block
  13. e.printStackTrace();
  14. }
  15. }

读方法

  1. private SerialData readData() {
  2. SerialData obj = null;
  3. FileInputStream fis;
  4. ObjectInputStream ois;
  5. try {
  6. fis = getApplicationContext().openFileInput(TAG);
  7. ois = new ObjectInputStream(fis);
  8. obj = (SerialData) ois.readObject();
  9. ois.close();
  10. fis.close();
  11. } catch (Exception e) {
  12. // TODO Auto-generated catch block
  13. e.printStackTrace();
  14. }
  15. if (obj != null)
  16. return obj;
  17. else
  18. return null;
  19. }

二.Parcelable

实现类
记事本 - 图6

  1. class ParceData implements Parcelable {
  2. public ArrayList<String> array;
  3. public ParceData() {
  4. array = new ArrayList<String>();
  5. for (int i = 0; i < 100000; i++) {
  6. array.add(new String("123"));
  7. }
  8. }
  9. @Override
  10. public int describeContents() {
  11. return 0;
  12. }
  13. @Override
  14. public void writeToParcel(Parcel parcel, int i) {
  15. parcel.writeStringList(array);
  16. }
  17. public static final Parcelable.Creator<ParceData> CREATOR = new Creator<ParceData>() {
  18. @Override
  19. public ParceData[] newArray(int size) {
  20. return new ParceData[size];
  21. }
  22. @Override
  23. public ParceData createFromParcel(Parcel in) {
  24. return new ParceData(in);
  25. }
  26. };
  27. public ParceData(Parcel in) {
  28. array = new ArrayList<String>();
  29. in.readStringList(array);
  30. }
  31. }

写方法

  1. protected void saveParce() {
  2. FileOutputStream fos;
  3. try {
  4. fos = getApplicationContext().openFileOutput(TAG,
  5. Context.MODE_PRIVATE);
  6. BufferedOutputStream bos = new BufferedOutputStream(fos);
  7. Parcel parcel = Parcel.obtain();
  8. parcel.writeParcelable(new ParceData(), 0);
  9. bos.write(parcel.marshall());
  10. bos.flush();
  11. bos.close();
  12. fos.flush();
  13. fos.close();
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. }

读方法

  1. protected void loadParce() {
  2. FileInputStream fis;
  3. try {
  4. fis = getApplicationContext().openFileInput(TAG);
  5. byte[] bytes = new byte[fis.available()];
  6. fis.read(bytes);
  7. Parcel parcel = Parcel.obtain();
  8. parcel.unmarshall(bytes, 0, bytes.length);
  9. parcel.setDataPosition(0);
  10. ParceData data = parcel.readParcelable(ParceData.class.getClassLoader());
  11. fis.close();
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. }

三.速度比较

1.Serializable
执行如下操作:
SerialData data = new SerialData();
Log.e(“time”, “befor = “ + System.currentTimeMillis());
saveAsSerializable(data);
Log.e(“time”, “after = “ + System.currentTimeMillis());
记事本 - 图7
10次平均:2889ms

2.Parcelable
操作如下:
Log.e(“time”, “befor = “ + System.currentTimeMillis());
saveParce();
Log.e(“time”, “after = “ + System.currentTimeMillis());

记事本 - 图8
10次平均:360ms

四.内存比较

初始状态:
记事本 - 图9


执行一次Serializable的写入
记事本 - 图10
记事本 - 图11
执行一次Parcelable的写入
记事本 - 图12



Pacelable在速度上,内存的使用上都比Serializable有优势。虽然如此,实际上面的数据并没有太大的参考意义,两个类都创建了10万个对象,项目中这种情况很少遇到。
实现起来Serializable更简单,实现接口即可,而Pacelable作二进制的转化,对写入和读取的顺序都要保持一致,对象越多实现越复杂。
记事本 - 图13

Gson

添加serializedName注解,这是gson的注解,意思是序列化时的名字,json映射的是这个名字,而不是字段名,不加这个注解就是映射字段名。

使用gson生成json数组

一般toJson(Object src)方法就够用了。

  1. String[] names = {"jadyli", "Juliet"};
  2. String[] genders = {"male", "female"};
  3. int[] ages = {18, 20};
  4. List<Student> students = new ArrayList<>();
  5. for (int i = 0; i < names.length; i++) {
  6. Student student = new Student();
  7. student.setName(names[i]);
  8. student.setGender(genders[i]);
  9. student.setAge(ages[i]);
  10. students.add(student);
  11. }
  12. String jsonStr = new Gson().toJson(students);
  13. System.out.println(jsonStr);

new Gson().toJson(Object src)输出的是没有格式化的json字符串,要是想输出格式化了的,可以使用

Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonStr = gson.toJson(students);

默认忽略空的字段,如果不想忽略,可以使用

Gson gson = new GsonBuilder().serializeNulls().create();
String jsonStr = gson.toJson(students);

Android控件属性 - gravity、layout_gravity、textAlignment的区别

gravity设置的是控件内部的子控件或文本的停靠位置,layout_gravity设置的是控件在其容器中的停靠位置。

textAlignment专门是指定文本的停靠(对齐)位置,可选值有”inherit”、”gravity”、”textStart”、”textEnd”、”center”、”viewStart”、”viewEnd”。对于TextView等控件,textAlignment的优先级高于gravity,而textAlignment为空或为”gravity”时,文本的对齐方式就由gravity属性确定。**

Nested weights are bad for performance警告

原因分析:在布局进行嵌套使用时,父布局与子布局都使用了android:layout_weight。


布局权重要求对小部件进行两次测量。当具有非零权重的LinearLayout嵌套在具有非零权重的另一个LinearLayout中时,度量的数量将呈指数增长。


最好使用RelativeLayout并根据其他视图的位置调整视图,而不使用特定的dpi值。

两分钟理解Android中PX、DP、SP的区别

px : 其实就是像素单位,比如我们通常说的手机分辨列表800*400都是px的单位
sp : 同dp相似,还会根据用户的字体大小偏好来缩放
dp : 虚拟像素,在不同的像素密度的设备上会自动适配
dip: 同dp

APP设计尺寸解读:px、pt、ppi、dpi、dp、sp之间的关系
**px:pixel———————————— 【 像素】 电子屏幕上组成一幅图画或照片的最基本单元
pt: point—————————————【 点】印刷行业常用单位,等于1/72英寸
ppi: pixel per inch————————【每英寸像素数】 该值越高,则屏幕越细腻
dpi: dot per inch—————————【每英寸多少点】,该值越高,则图片越细腻
dp: dip,Density-independent pixel, 【安卓开发用的长度单位】
1dp表示在屏幕像素点密度为160ppi时1px长度
sp: scale-independent pixel————————————【安卓开发用的字体大小单位】。
记事本 - 图14
记事本 - 图15

公式四: 1dp=(屏幕ppi/ 160)px


RecyclerView设置两个不同布局的注意事项

You should extends RecyclerView.Adapter<RecyclerView.ViewHolder> as onCreateViewHolder is returning RecyclerView.ViewHolder

  1. public class ItemCatgorySubListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>

定义类的时候要用RecyclerView.Adapter
onCreateViewHolder 的返回值也要使用**RecyclerView.ViewHolder**
在绑定数据时根据对应的ViewHolder实行强制数据类型转换。

如果是想在同一个页面显示不同item,需要Override
参考[RecyclerView的使用(2)之多Item布局的加载**](https://blog.csdn.net/leejizhou/article/details/50708349)

TabLayout+ViewPager+Fragment

TabLayout+ViewPager+Fragment实现切页展示

TabLayout+ViewPager+Fragment方法的使用流程:

  1. 创建存储多个Fragment实例的列表
  2. 创建PagerAdapter实例并关联到Viewpager中
  3. 将ViewPager关联到Tablayout中
  4. 根据需求改写Tablayout属性*

    懒加载策略

    Android的View绘制流程是最消耗CPU时间片的操作,尤其是在ViewPager缓存Fragment的情况下,如果在View绘建的同时还进行多个Fragment的数据加载,那用户体验简直是爆炸(不仅浪费流量,而且还造成不必要的卡顿)。。。因此,需要对Fragment们进行懒加载策略。什么是懒加载?就是被动加载,当Fragment页面可见时,才从网络加载数据并显示出来。

当Fragment的可见状态发生变化时就会调用这个函数,boolean参数isVisibleToUser代表当前的Fragment是否可见。
如果这么简单地调用函数就能实现懒加载的话,那也没什么好说的,但是这里又有一个巨坑,则是因为这个setUserVisibleHint函数是游离在Fragment生命周期之外的,它的执行有可能早于onCreate和onCreateView,然而既然要时间数据的加载,就必须要在onCreateView创建完视图过后才能使用,不然就会返回空指针崩溃,懒加载的重点也是在这儿,那么我们来分析,实行懒加载必须满足哪些条件呢?

1.View视图加载完毕,即onCreateView()执行完成 2.当前Fragment可见,即setUserVisibleHint()的参数为true 3.初次加载,即防止多次滑动重复加载


ViewPager中获取当前界面的Fragment注意事项

尝试解决:使用FragmentManager.findFragmentById()但出现问题

onPageSelected方法调用在setPrimaryItem之前,所以在onPageSelected方法里面去获取当前界面的fragment会是之前保存的状态。onPageSelected方法在切换执行一次,后者可能执行多次。
**这种方式有一个缺陷 setPrimaryItem()是在 viewpager的滑动监听执行完后才会调用的;所以在滑动监听中获取当前显示的Fragment 是不对的。

Android总结之Fragment

Fragment懒加载

1 定义三个变量分别记录三个条件状态:isViewCreated,isVisibleToUser,isLoaded
2 在适当的回调方法中,更新这三个变量
3 触发懒加载时,要判断三个条件都满足时,才加载数据。

Android基础:Fragment,看这篇就够了

Fragment的优势有以下几点:

  • 模块化(Modularity):我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中。
  • 可重用(Reusability):多个Activity可以重用一个Fragment。
  • 可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好

记事本 - 图16

Fragment和View的比较

Fragment和View都有助于界面组件的复用,这在大型工程里边是特别重要的,但是二者又有所区别。

1、Fragment的复用粒度更大。Fragment有完整的生命周期,从代码设计角度讲可以提高内聚性,不同情况下还可以设计不同的Fragment,比如横屏和竖屏情况下View的显示不一样,那么可以建立2个不同的Fragment去处理,代码上面可以有效的扩展。

从形态上讲和Activity更为接近,当然从编程角度上看也比View更为复杂。但是Fragment可以组装更多的View同一展示,而且生命周期有助于资源的管理。

2、简单的直接view,复杂的才用fragment,fragment资源消耗比较大。

3、一个fragment必须总是绑定到一个activity中,虽然fragment有自己的生命周期,但同时也被它的宿主activity的生命周期直接影响。
大部分情况下,Fragment用来封转UI的模块化组件;但是也可以创建没有UI的Fragment来提供后台行为,该行为会一直持续到Activity重新启动。这特别适合于定期和UI交互的后台任务或者当因配置改变而导致Activity重新启动是,保存状态变得特别重要的场合。

注意:当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。

[整] Android Fragment 生命周期图

fragments的大部分状态都和activitie很相似,但fragment有一些新的状态。

  • onAttached() —— 当fragment被加入到activity时调用(在这个方法中可以获得所在的activity)。
  • onCreateView() —— 当activity要得到fragment的layout时,调用此方法,fragment在其中创建自己的layout(界面)。
  • onActivityCreated() —— 当activity的onCreated()方法返回后调用此方法
  • onDestroyView() —— 当fragment中的视图被移除的时候,调用这个方法。
  • onDetach() —— 当fragment和activity分离的时候,调用这个方法。

一旦activity进入resumed状态(也就是running状态),你就可以自由地添加和删除fragment了。因此,只有当activity在resumed状态时,fragment的生命周期才能独立的运转,其它时候是依赖于activity的生命周期变化的。

setChecked方法触发onCheckedChanged监听器问题

在recyclerview onCheckedChanged自动调用

  1. @Override
  2. public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  3. //防止初始化的时候出发监听
  4. if (!buttonView.isPressed()) {
  5. return;
  6. }
  7. doSomeThing();
  8. }

LitePal设置默认值的注意事项

image.png

对于LitePal数据库而言,存储boolean值为true的时候可以直接用LitePal存储对象的方式存储,若存储的对象中boolean为false,需要调用setToDefault(String name)方法存储,其中参数name为对象中Boolean属性的名称,为字符串。

解决Android中TextView多行显示的最后一行被遮住一半的bug

将TextView的PaddingButtom设置一个切当的值即可解决(单行文字高度的一半就可以,例如10dp)。

android判断输入框EditText是否为空

一,通过判断输入值长度是否为零

  1. String serch_textip=mEdit1.getText().toString().trim();
  2. if(serch_textip.length()==0) //判断IP输入框是否为空
  3. {Toast.makeText(MainActivity.this,"请输入ip", Toast.LENGTH_LONG).show();}

二,比较输入值是否为Empty

  1. String serch_textip=mEdit1.getText().toString().trim();
  2. if(serch_textip.isEmpty()) //判断IP输入框是否为空
  3. {Toast.makeText(MainActivity.this,"请输入IP", Toast.LENGTH_LONG).show();}

三,比较输入值是否为null或者“ ”

  1. String serch_textinfo =mEdit.getText().toString();
  2. if(null == serch_textinfo || "".equals(serch_textinfo))
  3. {Toast.makeText(this,"请输入Info", Toast.LENGTH_LONG).show();} //判断Info输入框是否为空

string.trim()究竟去掉了什么?

trim()方法实际上trim掉了字符串两端Unicode编码小于等于32(\u0020)的所有字符

trim()方法实际上的行为并不是”去掉两端的空白字符“,而是”截取中间的非空白字符“

android EditText最多显示多高,超出的滑动显示

android:maxHeight=”120dp”

Android常见输入inputType类型

android中showSoftInput不起作用

Android 手动显示和隐藏软键盘

1、方法一(如果输入法在窗口上已经显示,则隐藏,反之则显示)

InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);

2、方法二(view为接受软键盘输入的视图,SHOW_FORCED表示强制显示)

InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view,InputMethodManager.SHOW_FORCED);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0); //强制隐藏键盘

3、调用隐藏系统默认的输入法

  1. ((InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(WidgetSearchActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); (WidgetSearchActivity是当前的Activity)

4、获取输入法打开的状态

InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
boolean isOpen=imm.isActive();//isOpen若返回true,则表示输入法打开

Android软键盘弹出时不把布局顶上去的解决方法

Android:windowSoftInputMode=”stateVisible|adjustResize”,这样会让屏幕整体上移
Android:windowSoftInputMode=”adjustPan”这样键盘就会覆盖屏幕。

android:windowSoftInputMode

Activity 的主窗口与包含屏幕软键盘的窗口之间的交互方式。该属性的设置会影响两点内容:

  • 当 Activity 成为用户注意的焦点时,软键盘的状态为隐藏还是可见。
  • 对 Activity 主窗口所做的调整 — 是否将其尺寸调小,为软键盘腾出空间;或当软键盘遮盖部分窗口时,是否平移其内容以使当前焦点可见。

该设置必须是下表所列的其中一项值,或一个“state...”值加上一个“adjust...”值的组合。在任一组中设置多个值(例如,多个“state...”值)均会产生未定义的结果。各值之间使用竖线 (|) 进行分隔。例如:

以下设置的值(“stateUnspecified”和“adjustUnspecified”除外)会替换主题中设置的值。

描述
stateUnspecified
不指定软键盘的状态(隐藏还是可见)。系统会选择合适的状态,或依赖主题中的设置。
这是对软键盘行为的默认设置。
stateUnchanged
当 Activity 转至前台时保留软键盘最后所处的任何状态,无论是可见还是隐藏。
stateHidden
当用户选择 Activity 时(换言之,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时)隐藏软键盘。
stateAlwaysHidden
当 Activity 的主窗口有输入焦点时始终隐藏软键盘。
stateVisible
在正常的适宜情况下(当用户向前导航到 Activity 的主窗口时)显示软键盘。
stateAlwaysVisible
当用户选择 Activity 时(换言之,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时)显示软键盘。
adjustUnspecified
不指定 Activity 的主窗口是否通过调整尺寸为软键盘腾出空间,或者是否通过平移窗口内容以在屏幕上显示当前焦点。根据窗口的内容是否存在任何可滚动其内容的布局视图,系统会自动选择其中一种模式。如果存在这种视图,系统会调整窗口尺寸,前提是可通过滚动操作在较小区域内看到窗口的所有内容。
这是对主窗口行为的默认设置。
adjustResize
始终调整 Activity 主窗口的尺寸,以为屏幕上的软键盘腾出空间。
adjustPan
不通过调整 Activity 主窗口的尺寸为软键盘腾出空间。相反,窗口的内容会自动平移,使键盘永远无法遮盖当前焦点,以便用户始终能看到自己输入的内容。这通常不如调整窗口尺寸可取,因为用户可能需关闭软键盘才能进入被遮盖的窗口部分,并与之进行交互。

该属性是 API 级别 3 中的新增属性。

RecycleView 点击事件

关于recycleview点击事件的问题 #608
一开始我接触的就是通过Adapter设置RecycleView的点击事件监听函数.但是发现其实这样相当于把点击事件从RecycleView里面分离出来.然后在后期发现可以在onTouchEvent函数里面拦截触摸事件然后判断是否触发点击事件.在一些场景下这两钟情况都能很好的把问题处理好.直到现在我的布局文件里面还包含更多的控件的时候,发现第二种办法的弊端.然后把这两种实现方法的原理细看一遍,发现其实他们实现的原理是赤裸裸的不一样.

通过Adapter设置点击事件

通过Adapter设置点击事件,很好理解.本质就是给View绑定事件监听函数.所以可以看到RecycleView是在Adapter#onCreateViewHolder函数里面构造出我们需要的View.当然在这个时候,我们可以在Adapter#onCreateViewHolder或者Adapter#onBindViewHolder函数里面设置点击事件监听函数即可.
在onCreateViewHolder函数里面设置点击事件监听函数

  1. public RvAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  2. View view;
  3. final RvAdapter.ViewHolder holder;
  4. view = mLayoutInflater.inflate(R.layout.item_simple_textview, parent, false);
  5. holder = new ViewHolder(view);
  6. view.findViewById(R.id.txtTag).setOnClickListener(new View.OnClickListener() {
  7. @Override
  8. public void onClick(View view) {
  9. Log.i(TAG, mList.get(holder.getLayoutPosition()) + " is click");
  10. }
  11. });
  12. return holder;
  13. }
  1. 在构造View的同时给View绑定点击事件监听函数,并在监听函数里面持有holder的引用.当回调监听函数的时候通过ViewHolder#getLayoutPosition函数获取position.<br />**操控控件数据时,要用holder的数据设置,不能用view的,否则空指针。**

在onBindViewHolder函数里面设置设置点击事件监听函数
public void onBindViewHolder(final RvAdapter.ViewHolder holder, final int position) {
holder.txtTag.setText(mList.get(position));

  1. holder.itemView.findViewById(R.id.txtTag).setOnClickListener(new View.OnClickListener() {<br /> @Override<br /> public void onClick(View view) {<br /> Log.i(TAG, mList.get(position) + " is click");<br /> }<br /> });<br /> }
  2. 在绑定事件里面给View绑定点击事件监听函数.

两种办法的区别是也是很明显的.对于第一种方法,因为onCreateViewHolder调用的次数有限的.只当需要创建ViewHolder的时候才会调用,所以我们知道它只需对每一个ViewHolder内含的View创建相应的点击事件监听函数就可以了.但是对于第二种方法,就是每次绑定数据的时候都会创建点击事件监听函数.当然喜欢怎么使用是大家的自由.

总结:通过Adapter设置点击事件,本质上是对View直接设置点击事件监听函数.
image.png

菜单

选项菜单和应用栏
参考:
使用方法:
方法一:添加菜单项:onCreateOptionsMenu(Menu menu)中添加menu.add(Menu.NONE,Menu.FIRST+1,5,”删除”).setIcon()———>添加选择菜 单项事件:在onOptionsItemSelected(MenuItem item)中 switch(item.getItemId()),然后添加对应选择事件———>添加关闭菜单事 件:onOptionsMenuClosed(Menu menu),在其中添加代码即可————>添加准备菜单(菜单显示之前的事件),在其中添加代码即可
方法二:添加菜单的样式:在res中创建menu目录,建立 xml,

———-> onCreateOptionsMenu(Menu menu)中 this.getMenuInflater().inflate(R.menu.options_menu,menu);
函数说明:

add()方法的四个参数,依次是:
1、组别,如果不分组的话就写Menu.NONE,
2、Id,这个很重要,Android根据这个Id来确定不同的菜单
3、顺序,那个菜单现在在前面由这个参数的大小决定,从1到6一次是同上往下,从左到右排。
4、文本,菜单的显示文本
setIcon(图片路径):为菜单设置图标
getMenuInflater().inflate(R.menu.options_menu,menu):
a.inflate的作用是将xml定义的而一个布局找出来,但仅仅是找出来。
b.第一个参数是布局,第二个参数是菜单。**上下文菜单和上下文操作模式
**弹出菜单

Android同一Activity中不同Fragment设置不同的menu(ViewPager+Fragment)

onPrepareOptionsMenu是每次在display Menu之前,都会去调用,只要按一次Menu按鍵,就会调用一次。所以可以在这里动态的改变menu。一般使用这个函数时先执行menu.clear().否则如果在这里执行add()的话会不停的追加。

TextToSpeech使用无效问题

创建TextToSpeech实例时会创建服务并绑定到Activity或者Service(取决于在哪里创建实例),所以在销毁这个服务之前应该调用stop()和shutdown()方法来释放TextToSpeech所使用的本地资源。同时,如果使用的是单例模式,必须置空singleton。以防另一个服务需要使用单例的时候,出现问题。

同时,如果在onCreate方法初始化TextToSpeech之后,调用textToSpeech.speak,可能发不出声音,因为textToSpeech可能还没初始化完,所以可以适当延时。或者把代码放到onInit方法里面的else里面执行,即初始化成功后再执行。
可以尝试Application的上下文初始化。

参考资料:Android调用自带TTS文本转语音引擎实现离线语音合成
TextToSpeech的使用

2020-02-24 20:35:49.784 4200-4200/com.example.note E/ActivityThread: Service com.example.note.service.AlarmService has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@7079c34 that was originally bound here android.app.ServiceConnectionLeaked: Service com.example.note.service.AlarmService has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@7079c34 that was originally bound here at android.app.LoadedApk$ServiceDispatcher.(LoadedApk.java:1334) at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:1229) at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1466) at android.app.ContextImpl.bindService(ContextImpl.java:1438) at android.content.ContextWrapper.bindService(ContextWrapper.java:639) at android.speech.tts.TextToSpeech.connectToEngine(TextToSpeech.java:810) at android.speech.tts.TextToSpeech.initTts(TextToSpeech.java:780) at android.speech.tts.TextToSpeech.(TextToSpeech.java:733) at android.speech.tts.TextToSpeech.(TextToSpeech.java:712) at android.speech.tts.TextToSpeech.(TextToSpeech.java:696) at com.example.note.util.TtsUtil.(TtsUtil.java:31) at com.example.note.util.TtsUtil.getInstance(TtsUtil.java:22) at com.example.note.service.AlarmService.onCreate(AlarmService.java:30) at android.app.ActivityThread.handleCreateService(ActivityThread.java:3405) at android.app.ActivityThread.-wrap5(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1753) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:159) at android.app.ActivityThread.main(ActivityThread.java:6385) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1096) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:883)

TextToSpeech的UtteranceProgressListener触发问题

image.png

发通知 PendingIntent 中Intent 内容没有更新

PendingIntent中定义了几个FLAG
android.app.PendingIntent.FLAG_UPDATE_CURRENT
如果PendingIntent已经存在,保留它并且只替换它的extra数据。

PendingIntent中Flags的参数设置

Android延时操作方法

一、使用线程的休眠

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. try {
  5. Thread.sleep(5000);//休眠5秒
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. //延时执行的代码
  10. }
  11. }).start();

二、使用TimerTask实现延时操作

  1. Timer timer = new Timer();
  2. timer.schedule(new TimerTask() {
  3. @Override
  4. public void run() {
  5. /**
  6. * 延时执行的代码
  7. */
  8. }
  9. },1000); // 延时1秒

三、使用Handler的postDelayed()方法

  1. new Handler().postDelayed(new Runnable() {
  2. @Override
  3. public void run() {
  4. /**
  5. * 延时执行的代码
  6. */
  7. }
  8. },1000); // 延时1秒

注:由于前两种在更新UI时,如果不使用消息处理机制的话,会报如下异常:Only the original thread that created a view hierarchy can touch its views.(只能在主线程中更新UI),为了避免这种错误出现,在使用延时操作的时候推荐使用第三种。

参考Android 延时操作的三种方式

Looper的无限循环为啥不会导致主线程阻塞

Intent传递数据和Bundle传递数据的区别

Intent传递数据和Bundle传递数据是一回事,
Intent传递时内部还是调用了Bundle。

  1. public @NonNull Intent putExtra(String name, int value) {
  2. if (mExtras == null) {
  3. mExtras = new Bundle();
  4. }
  5. mExtras.putInt(name, value);
  6. return this;
  7. }

Bundle类用作携带数据,它类似于Map,用于存放key-value名值对形式的值。相对于Map,它提供了各种常用类型的putXxx()/getXxx()方法,如:putString()/getString()和putInt()/getInt(),putXxx()用于往Bundle对象放入数据,getXxx()方法用于从Bundle对象里获取数据。Bundle的内部实际上是使用了HashMap类型的变量来存放putXxx()方法放入的值。简单地说,Bundle就是一个封装好的包,专门用于导入Intent传值的包。

面试常客:Intent 能传递多大 Size 的数据?

记事本 - 图20

一、序

作为 Android 开发,日常 Coding 时,最频繁的操作应该就是操作 App 内的一系列 Activity。而在 Activity 间传递数据,就需要借助 Intent。
不少资料中写到,Intent 在 Activity 间传递基础类型数据或者可序列化的对象数据。但是 Intent 对数据大小是有限制的,当超过这个限制后,就会触发 TransactionTooLargeException 异常。
那么今天就来聊聊 Intent 传递大数据时,为什么会抛异常,以及如何解决它。

二、为什么会出现异常?

2.1 异常原因

Intent 传递大数据,会出现 TransactionTooLargeException 的场景,这本身也是一道面试题,经常在面试中被问到。
记事本 - 图21
其实这个问题,如果遇到了,查查文档就知道了。
在 TransactionTooLargeException(https://developer.android.com/reference/android/os/TransactionTooLargeException.html) 的文档中,其实已经将触发原因详细说明了。
简单来说,Intent 传输数据的机制中,用到了 Binder。Intent 中的数据,会作为 Parcel 被存储在 Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输。
而这个 Binder 事务缓冲区具有一个有限的固定大小,当前为 1MB。你可别以为传递 1MB 以下的数据就安全了,这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。也就是说 Intent 在 Activity 间传输数据,本身也不适合传递太大的数据。

2.2 Bundle 的锅?

这里再补充一些细节,Intent 使用 Bundle 存储数据,到底是值传递(深拷贝)还是引用传递?
Intent 传输的数据,都存放在一个 Bundle 类型的对象 mExtras 中,Bundle 要求所有存储的数据,都是可被序列化的。
在 Android 中,序列化数据需要实现 Serializable 或者 Parcelable。对于基础数据类型的包装类,本身就是实现了 Serializable,而我们自定义的对象,按需实现这两个序列化接口的其中一个即可。
那是不是只要通过 Bundle 传递数据,就会面临序列化的问题?
并不是,Activity 之间传递数据,首先要考虑跨进程的问题,而 Android 中又是通过 Binder 机制来解决跨进程通信的问题。涉及到跨进程,对于复杂数据就要涉及到序列化和反序列化的过程,这就注定是一次值传递(深拷贝)的过程。
这个问题用反证法也可以解释,如果是引用传递,那传递过去的只是对象的引用,指向了对象的存储地址,就只相当于一个 Int 的大小,也就根本不会出现 TransactionTooLargeException 异常。
传输数据序列化和 Bundle 没有关系,只与 Binder 的跨进程通信有关。
为什么要强调这个呢?
在 Android 中,使用 Bundle 传输数据,并非 Intent 独有的。例如使用弹窗时,DialogFragment 中也可以通过 setArguments(Bundle) 传递一个 Bundle 对象给对话框。
Fragment 本身是不涉及跨进程的,这里虽然使用了 Bundle 传输数据,但是并没有通过 Binder,也就是不存在序列化和反序列化。和 Fragment 数据传递相关的 Bundle,其实传递的是原对象的引用。
有兴趣可以做个试验,弹出 Dialog 时传递一个对象,Dialog 中修改数据后,在 Activity 中检查数据是否被修改了。

三、如何解决这个异常?

3.1 解决思路

知道异常的原因,就好解决了。
既然原因在于 Binder 传输限制了数据的大小,那我们不走 Binder 通信就好了。
可以从数据源上来考虑。
例如 Bitmap,本身就已经实现了 Parcelable 是可以支持序列化的。用 Intent 传输,稍微大一点的图一定会出现 TransactionTooLargeException。当然真是业务场景,肯定不存在传递 Bitmap 的情况。
那就先看看这个图片的数据源。Drawable?本地文件?线上图片?无论数据源在哪里,我们只需要传递一个 drawable_id、路径、URL,就可以还原这张图片,无需将这个 Bitmap 对象传递过去。大数据总有数据源,从数据源还原数据,对我们而言只是调用一个方法而已。
此前阿里发布的《Android 开发者手册》中,就提到了这个问题的解决建议。
记事本 - 图22
阿里给出的方案,是通过 EventBus 来传递数据。

3.2 EventBus 的 粘性事件

很多商业项目其实都用到了 EventBus,这里就简单介绍如何使用 EventBus 的粘性事件来完成数据在 Activity 间的传递。
EventBus 是一个 Android 端优化的 Publish/subscribe 消息总线,简化了应用程序内各个组件间、组件与后台线程间的通信。
在 Activity 中使用 EventBus,需要根据 Activity 的生命周期,成对调用 register()unregister() 方法。普通的事件,只会发生在 register() 之后,在注册前发生的事件,统统都收不到。
这里利用的 EventBus 的粘性事件(Sticky Event)来实现,EventBus 内部维护了一个 Map 对象 stickyEvents,用于缓存粘性事件。
粘性事件使用 postSticky() 方法发送,它会将事件缓存到 stickyEvents 这个 Map 对象中,以待下次注册时,将这个事件取出,抛给注册的组件。以此来达到一个粘性的滞后事件发送和接收。
接下来我们看看 EventBus 粘性事件的使用细节。
1. 注解的区别
粘性事件的注解和普通事件的注解略有区别,需要添加 threadMode 和 sticky 参数。

  1. @Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
  2. public void onStickyEvent(MyStickyEvent event){
  3. //...
  4. }

注意,这两个额外的参数是必须的。
2. 调用方法的区别
在发送消息的时候,需要使用 postSticky() 来替换掉 post() 方法。
需要注意的是,粘性事件是使用 Map 结构缓存的,并且是使用事件对象类型当 Key 进行缓存,所以对于同类型的数据,它只会缓存最后发送的数据。
3. 注意清理事件
前面也提到,粘性事件是存储在一个 Map 对象中的,它是不会主动清理其中存储的对象的,需要开发者手动清理。
EventBus 提供了两类方法 removeStickyEvent()removeAllStickyEvents() 方法,分别用来清理固定数据以及全部数据。
我们需要在合适的时机,手动的调用这两类方法,清理粘性事件。如果不对粘性事件进行清理,每次 register() 的时候,都会收到粘性事件。
4. EventBus 粘性事件的问题
粘性事件本身是脱离了 Android Intent 数据传递的这一套机制的,要知道 Activity 会在一些特殊情况下被销毁重建,在此情况下,通过 Intent 传递的数据,是可以继续从 Intent 中获取恢复到上一次页面传递的数据。
而通过 EventBus 的粘性事件,则可能在销毁重建时,造成数据丢失。
如果想要使用 EventBus 的粘性事件,来在页面间传递大数据,还是有不少细节,需要根据业务来调整的。

四、小结时刻

今天我们聊到了在 Activity 间,通过 Intent 传递大数据会触发 TransactionTooLargeException 异常的原因,以及如何解决它,最后再简单总结一下。

  1. Intent 无法传递大数据是因为其内部使用了 Binder 通信机制,Binder 事务缓冲区限制了传递数据的大小。
  2. Binder 事务缓冲区的大小限定在 1MB,但是这个尺寸是共享的,也就是并不是传递 1MB 以下的数据就绝对安全,要视当前的环境而定。
  3. 不要挑战 Intent 传递数据大小的极限,对于大数据,例如长字符串、Bitmap 等,不要考虑 Intent 传递数据的方案。
  4. 解决大数据传递问题,可以从数据源出发,根据数据的标识,还原数据,或者先持久化再还原。也可以使用 EventBus 的粘性事件来解决。

    reference:https://developer.android.com/reference/android/os/TransactionTooLargeException.html https://www.wanandroid.com/blogimgs/a2609aed-1000-4039-93c3-7541aaa2013b.pdf

【架构】Android较为理想的界面同步机制

深入了解架构组件之ViewModel

[LiveData](https://developer.android.com/reference/androidx/lifecycle/LiveData?hl=zh-cn) 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

自己动手——快速搭建Android应用服务器

ContentLoadingProgressBar

ContentLoadingProgressBar实现了一个ProgressBar,它在显示之前等待最少的时间被消除。一旦可见,进度条将在最短的时间内可见,从而避免事件可能花费很大的时间(从无到用户可感知的时间)完成时,避免在UI中出现“闪烁”

ProgressDialog过时(废弃)的替代方案

如果想模仿ProgressDialog出现时,用户无法与界面继续交互的效果

  1. getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

恢复用户与界面的交互
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

线程的中断(interrup)

现在已经不提倡使用stop方法来停止线程,因为那是不安全的,最好的停止线程方式是定义一个类成员变量,然后在循环过程中去判断此变量是否为假,便退出当前循环。但如果线程正在sleep()或wait()中,便无法使用类成员变量来判断,此时可以使用interrup()方法离开run()方法,同时结束线程,但程序会抛出InterruptedException异常。使用如下:

当在开发中遇到需要在activity关闭的时候关闭线程时:
1.可以在线程的Run方法里面设置标记手动关闭. (使用停止的标记位,那么可以保正在真正停止之前完整的运行完)
2.调用Thread.stop()方法也可以,但是会出问题(使用Thread.stop方法停止线程,不能保证这个线程是否完整的运行完成一次)

  1. // 线程类
  2. class MyThread extends Thread {
  3. volatileboolean mStop = false; //volatile 用来修饰被不同线程访问和
  4. @Override
  5. public void run() {
  6. while(!mStop) {
  7. try{
  8. Thread.sleep(1000);
  9. // TODO...
  10. }catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. }
  16. // 启动线程
  17. MyThread myThread = new MyThread();
  18. myThread.start();
  19. // 停止线程
  20. myThread.mStop = true;
  21. myThread.interrupt();

还有另外一个思路来让线程可以及时回收,我们知道context对象与activity是绑定的,我们可以实例application来暂存当前context与当前context进行比较,我们可以优化上面的代码,具体代码如下:

自定义application用来暂存context对象:

public class MyApplication extends Application {
static Context appContext;

@Override
public void onCreate() {
super.onCreate();
}

public static void setContext(Context context) {
appContext = context;
}
}
让线程去做context比较,这样我们就可以忽略activity的生命周期:
private MyThread myThread;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

MyApplication.setContext(this);
myThread = new MyThread(this);
myThread.start();

}

private class MyThread extends Thread {

private boolean stop = false;

private Context context;

public MyThread(Context context) {
this.context = context;
}

@Override
public void run() {
super.run();
while (context == MyApplication.appContext) {
try {
Thread.sleep(1000);
Log.i(“———-“, “running”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

线程礼让(yield)

sleep()方法使当前运行中的线程睡眠一段时间,这段时间的长短是由参数设定的,yield()方法使当前线程让出CPU占有权,但让出的时间是不可设定的,而且也不会释放锁标志。

使用yield()方法可使同样优先级的线程有进入可执行状态的机会,如果没有相同优先级的线程或者被礼让线程放弃执行权时,原线程会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用yeild()方法,因为操作系统会为线程自动分配CPU时间片来执行。

线程的优先级

Thrread类中包含的成员变量代表了线程的某些优先级,比如Thread.MIN_PRIORITY(常数1)、Thread.MAX_PRIORITY(常数2)、Thread.NORM_PRIORITY(常数5)。其中每个线程的优先级都在Thread.MIN_PRIORITY ~ Thread.MAX_PRIORITY之间,默认是Thread.NORM_PRIORITY。

线程的优先级可使用setPriority()方法调整,如果使用该方法设置的优先级不在1~10之内,将产生IllegalArgumentException异常。

线程同步(synchronized)

单一程程序中,每次只能做一件事情,它是串行执行的;但多线程程序是可以异步并发处理同一件事情,这样就会发生两个线程抢占资源的问题,使如一个线程定在写数据,而另一个线程刚才在读数据,那么就会产生很多未知的错误情况出现,这也是多线程最危险的事情。

同步块

Java中提供了同步机制,可以有效地防止资源冲突。使用关键字synchronized。如:

  1. public class Person {
  2. privateString mName;
  3. public void setName(String name) {
  4. synchronized(mName){
  5. this.mName = name;
  6. }
  7. }
  8. }

这里要锁定的是对象类成员变量mName,而且只有在同一个Person对象才行效,否则是互不影响的。比如Person A = new Person()和Person B = newPerson()中,A和B之间是不受synchronized制约的。

  1. public class Person{
  2. privateString mName;
  3. public void setName(String name) {
  4. synchronized(Person.class) {
  5. this.mName = name;
  6. }
  7. }
  8. }

上面这锁是对类级别的,锁定的是Person.class,不管是否同一对象,都会实行同步机制

使用synchronized用来修饰方法

基本执行过程:当多个线程同时访问被synchronized修饰的方法的时候,有且只有一个线程可以访问,当一个线程在访问的时候,其他线程只能等待。当一个线程访问完毕后下一个线程才可以访问。
原理解析:当方法被synchronized修饰后,如果想要执行该方法就必须获得相应的锁。
锁的说明:每个类又且仅有一个锁(针对静态方法),每个类的示例也是有且仅有一个锁。
当多个线程在同时访问同一个方法的时候,执行该方法就必须获得相应的锁,同时锁只有一个,所以只能有一个线程可以获得锁,所以只有一个线程可以执行,当方法执行完毕,线程会释放锁,其他线程获取该锁,执行方法。
进阶说明:由于每个类只有一个锁,所以当一个类中又多个方法被synchronized修饰的时候,在同一时间内只有一个方法可以获得锁,所以只有一个方法可以执行。

使用synchronized用来修饰代码块

基本说明:当synchronized在修饰代码块的时候需要一个自定义锁,当在多线程访问,代码块的时候,只要获得自定义锁就可以执行。
自定义锁:可以是一个类,也可以是一个实例(可以是Objcet的子类,也可以是当前类自己(this)),当具有相同自定锁的时候,代码块会顺序去执行,当锁不同的时候互相不影响。


synchronized(任意自定义对象)与synchronized同步方法共用:使用synchronized(任意自定义对象)进行同步操作,对象监视器必须是同一个对象。不过不是同一个,运行就是异步执行了。)

每个类又且仅有一个锁(针对静态方法),每个类的示例也是有且仅有一个锁。
基本书写格式:synchronized(自定义锁){同步执行代码块}

关于两种方法的使用:对于修饰方法常常会发生不必要的代码块被同步,同时锁不可以选择,但是使用 简单。对于修饰代码块方法用起来更精确,需要自己制定锁,用起来稍微麻烦一点。

作者:沈凤德
链接:https://www.jianshu.com/p/35af333fdde4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
image.png

静态同步synchronized方法与synchronized(*.class)代码块


synchronized应用在static方法上,那是对当前对应的*.Class进行持锁。
image.png

同步synchronized(*.class)代码块的作用其实和synchronized static方法作用一样。Class锁对类的所有对象实例起作用。
image.png

最典型的单例

  1. private volatile static Test sTest = null;
  2. private Test () {}
  3. public static Test getInstance() {
  4. if (sTest == null) {
  5. synchronized (Test.class) {
  6. if (sTest == null) {
  7. sTest = new Test();
  8. }
  9. }
  10. }
  11. return sTest;
  12. }

附加说明:
1.volatile修饰的变量,在一个线程中被改变时会立刻同步到主内存中,而另一个线程在操作这个变量时都会先从主内存更新这个变量的值。
2.第一个判空是为了避免不必要的同步,第二层判断是为了在null 情况下创建实例

线程间的通信

调用wait()方法和sleep()方法都可以使线程从运行状态进入就绪状态,而sleep()方法的线程是不释放锁的,wait()方法只能在同步块或同步方法中使用,wait()方法的线程是释放锁的。wait()可带参数或不带参数,其中wait(time)的情况跟sleep(time)一样,在一定时间内暂停;而如果wait()无参数情况下是永久无限地等待下去,需要使用notify()或notifyAll()方法才能唤醒。

异步转同步CountDownLatch

CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。简单地说就是有A和B两个方法都是异步执行,由于某些需求,想让A方法执行完之后再执行B方法。

Atomic系列对象

在多线程下,像i++、++i之类的操作都是不安全的,在使用时不可避免的会用到synchronized关键字进行锁定对象,而Atomic系统对象则通过一种线程安全的操作方法,是以原子方式更新对应值,从而可以不使用synchronized关键字情况下也能达到预期安全效果,而且效率还有可能比synchronized略高,Atomic系列对象有:AtomicBoolean、AtomicInteger、AtomicLong,等。

CopyOnWriteArrayList

CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。

这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set和add)不受支持。这些方法将抛出UnsupportedOperationException。允许使用所有元素,包括null。
参考

android 如何正确循环删除list中的数据

当我们使用for循环删除列表中的数据的时候,会存在问题,因为ArrayList的父类AbstractList里有个modCount的字段记录着List的总数,for循环的时候如果增加或者删除了元素,(修改不会影响),此字段会变化,那么在下次for循环的时候检查到跟之前的长度不同(删除后break循环没事),此时会报ConcurrentModificationException异常。

如何正确遍历删除List中的元素,你会吗?

1.通过增强的for循环删除符合条件的多个元素
2.通过增强的for循环删除符合条件的一个元素
3.通过普通的for删除删除符合条件的多个元素
4.通过Iterator进行遍历删除符合条件的多个元素

  1. import java.util.ArrayList;
  2. import java.util.Iterator;
  3. import java.util.List;
  4. public class ListRemove {
  5. public static void main(String args[]) {
  6. ListRemove lr = new ListRemove();
  7. lr.listRemove();
  8. lr.listRemoveBreak();
  9. // lr.listRemove2();
  10. // lr.iteratorRemove();
  11. }
  12. /**
  13. * 使用增强的for循环
  14. * 在循环过程中从List中删除元素以后,继续循环List时会报ConcurrentModificationException
  15. */
  16. public void listRemove() {
  17. List<Student> students = this.getStudents();
  18. for (Student stu : students) {
  19. if (stu.getId() == 2)
  20. students.remove(stu);
  21. }
  22. }
  23. /**
  24. * 像这种使用增强的for循环对List进行遍历删除,但删除之后马上就跳出的也不会出现异常
  25. */
  26. public void listRemoveBreak() {
  27. List<Student> students = this.getStudents();
  28. for (Student stu : students) {
  29. if (stu.getId() == 2) {
  30. students.remove(stu);
  31. break;
  32. }
  33. }
  34. }
  35. /**
  36. * 这种不使用增强的for循环,每次重新获取list的size遍历的情况运行时不会报错,但是可能删除的结果是错的。
  37. */
  38. public void listRemove2() {
  39. List<Student> students = this.getStudents();
  40. for (int i=0; i<students.size(); i++) {
  41. if (students.get(i).getId()%2 == 0)
  42. students.remove(i);
  43. }
  44. }
  45. /**
  46. * 使用Iterator的方式也可以顺利删除和遍历
  47. */
  48. public void iteratorRemove() {
  49. List<Student> students = this.getStudents();
  50. System.out.println(students);
  51. Iterator<Student> stuIter = students.iterator();
  52. while (stuIter.hasNext()) {
  53. Student student = stuIter.next();
  54. if (student.getId() % 2 == 0)
  55. stuIter.remove();
  56. }
  57. System.out.println(students);
  58. }
  59. private List<Student> getStudents() {
  60. List<Student> students = new ArrayList<Student>() {
  61. {
  62. int i = 0;
  63. while (i++ < 10) {
  64. Student student = new Student(i, "201200" + i, "name_" + i);
  65. this.add(student);
  66. }
  67. }
  68. };
  69. return students;
  70. }
  71. }

Fragment怎么直接调用Activity的方法

假如 父Activity的类名叫 MainActivity,有一个test()方法

  1. if(getActivity()instanceof MainActivity){
  2. ((MainActivity)getActivity()).test();
  3. }

也可以声明一个接口,让Activity实现它,然后在Fragment中判断Activity是否实现它,调用Activity方法。

Android Studio 中org.apache.http.legacy解决办法

Android Studio在build时,提示

Unable to find optional library: org.apache.http.legacy
是说找不到这个类。这是什么情况呢?
查阅文档发现,原来是Android 6.0不再支持 Apache HTTP client, 建议使用 HttpURLConnection 代替。如果还想要继续使用 Apache HTTP client 的,请在build.gradle中添加下面的代码

android {
useLibrary ‘org.apache.http.legacy’
}
重新同步build.gradle,问题就解决了!

android日历提醒

系统日历提醒的大大大的好处是:由于提醒功能是交付给系统日历来做,不会出现应用被杀的情况,会准时提醒需要的事件!!!

一种方法是不打开日历,直接写数据:(复杂,比如微博的日历提醒)
1.咱们的程序需要有读写日历权限;
2.如果咱们的程序没有日历账户需要先在日历系统创建自己的账户;
3.最后自己实现日历事件的增删改查、提醒功能;

另外一种方法:直接打开日历界面创建

  1. private static String calanderEventURL = null;
  2. static {
  3. if (Integer.parseInt(Build.VERSION.SDK) >= 8) {
  4. calanderEventURL = "content://com.android.calendar/events";
  5. } else {
  6. calanderEventURL = "content://calendar/events";
  7. }
  8. }
  9. private void OpenCalendar() {
  10. Calendar beginTime = Calendar.getInstance();//开始时间
  11. beginTime.clear();
  12. beginTime.set(2014,0,1,12,0);//2014年1月1日12点0分(注意:月份0-11,24小时制)
  13. Calendar endTime = Calendar.getInstance();//结束时间
  14. endTime.clear();
  15. endTime.set(2014,1,1,13,30);//2014年2月1日13点30分(注意:月份0-11,24小时制)
  16. Intent intent = new Intent(Intent.ACTION_INSERT)
  17. .setData(Uri.parse(calanderEventURL))
  18. .putExtra("beginTime", beginTime.getTimeInMillis())
  19. .putExtra("endTime", endTime.getTimeInMillis())
  20. .putExtra("title", "标题")
  21. .putExtra("description", "地点");
  22. startActivity(intent);
  23. }

关于drawable和drawable-v24

不同的drawable文件夹用于为设备兼容性和不同的Android版本提供不同的屏幕密度。

7.0的关于图片资源的改变

从Android API 24(安卓7.0)开始,自定义Drawables类可以最终在XML中使用(仅在您的包中)。

8.0的关于图片资源的改变

API 26(安卓8.0)中添加了VectorDrawable自适应图标

在StyleSpan中,从BOLD字体到NORMAL字体不生效

  1. startSelection = etx.getSelectionStart();
  2. endSelection = etx.getSelectionEnd();
  3. Editable editable = etx.getText();
  4. StyleSpan[] spans = editable.getSpans(startSelection, endSelection, StyleSpan.class);
  5. for (StyleSpan span : spans) {
  6. editable.removeSpan(span);
  7. }

NavigationView清除选中效果

将处于menu资源文件夹中对应菜单的属性值更改为即可。

修改NavigationView的MenuItem的字体颜色并修改默认选中的item

首先在res/color文件下创建一个selector文件navigation_menu_item_color.xml,内容如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <item android:state_checked="true"
  4. android:color="@color/colorAccent"></item> <!-- selected颜色 -->
  5. <item android:state_pressed="true"
  6. android:color="@color/colorAccent"/> <!-- pressed颜色 -->
  7. <item android:state_focused="true"
  8. android:color="@color/colorAccent"/> <!-- focused颜色 -->
  9. <item android:color="#FFFFFF"/> <!-- default颜色 -->
  10. </selector>
  1. /**设置MenuItem的字体颜色**/
  2. NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
  3. navigationView.setNavigationItemSelectedListener(this);
  4. Resources resource=(Resources)getBaseContext().getResources();
  5. ColorStateList csl=(ColorStateList)resource.getColorStateList(R.color.navigation_menu_item_color);
  6. navigationView.setItemTextColor(csl);
  7. /**设置MenuItem默认选中项**/
  8. navigationView.getMenu().getItem(0).setChecked(true);

Android监听当前Activity屏幕的触摸点击事件

当用户在某一页停留并且如果该用户在一段时间内没有点击或者触摸过屏幕,则弹窗提示用户已经长时间没有操作屏幕了.
查阅activity的方法,发现有dispatchTouchEvent()这个方法的Override 遂 开始干活!

在活动中重写这个方法。

Activity禁用全局点击事件

android 事件分发机制是从Activity-ViewGroup-View,层层分发的,通过activtiy的dispatchTouchEvent方法分发到ViewGroup的dispatchTouchEvent,ViewGroup通过调用onInterceptTouchEvent方法来判断是否拦截事件,如果不拦截就继续分发到View中,然后遍历ViewGrop中所有子View找到我们点击的View控件,将事件传递到该View的dispatchTouchEvent中。然后View会调用自身的onTouch方法,我们可以重写onTouch方法,进行拦截,点击事件。

具体分发机制可以看这边博客:Android事件分发机制详解:史上最全面、最易懂
当我们了解了事件分发机制后,如果现在又这样的一个需求,比如网络请求的时候,要禁止所有按钮的点击事件,比较笨的方法是,单独关闭按钮的点击事件。当我们了解了分发机制后,可以直接重写Activity 的dispatchTouchEvent方法,返回一个true,即可拦截所有的触摸时间,这样整个页面的按钮都不能被点击,(触摸优先级高于点击的优先级,点击事件只是触摸的一种特殊方式。),可以在activity基类重写 dispatchTouchEvent方法,然后设置一个变量来控制是否禁用全局点击事件

Android源码系列(9) — Activity事件分发

Android new File初识-创建不出文件问题

绝大部分人创建不出来情况如下:
1.是否具有存储权限,android6.0以上是否动态申请权限?
2.存储的路径是否有问题?
3.存储空间是否爆满?

  1. File dir = new File(SD_PATH);
  2. dir.mkdirs()

其中单执行new file并不会创建文件或文件夹,要通过调研mkdirs()函数,这样创建的目录不会删除以前的数据。其中要注意的是 mkdir()和mkdirs()的区别,前者不会创建父目录,若没有父目录的话会创建失败,所以推荐使用后者来创建。(一定要确保创建了文件夹,再去创建文件,否则文件会创建失败

  1. File dir1 = new File(dir,"image.jpg");

看英文文档以为会创建一个jpg的文件,然后实际操作中发现时不创建的,可以通过createNewFile()函数这样创建一个空的文件,也可以通过文件流的使用创建。

Android解决file.lastModified()一直为0的方法

如果您发送无效路径,那么您将始终获得0或1970的值!(因为Google将开始日期设置为该日期:))

getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir()的作用

  1. Environment.getDataDirectory() = /data
  2. Environment.getDownloadCacheDirectory() = /cache
  3. Environment.getExternalStorageDirectory() = /mnt/sdcard
  4. Environment.getExternalStoragePublicDirectory(“test”) = /mnt/sdcard/test
  5. Environment.getRootDirectory() = /system
  6. getPackageCodePath() = /data/app/com.my.app-1.apk
  7. getPackageResourcePath() = /data/app/com.my.app-1.apk
  8. getCacheDir() = /data/data/com.my.app/cache
  9. getDatabasePath(“test”) = /data/data/com.my.app/databases/test
  10. getDir(“test”, Context.MODE_PRIVATE) = /data/data/com.my.app/app_test
  11. getExternalCacheDir() = /mnt/sdcard/Android/data/com.my.app/cache
  12. getExternalFilesDir(“test”) = /mnt/sdcard/Android/data/com.my.app/files/test
  13. getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files
  14. getFilesDir() = /data/data/com.my.app/files

先判断SD卡是否可用,可用时优先使用SD卡的存储,不可用时用内部存储
存储在SD卡上时,可以在SD卡上新建任意一个目录存放,也可以存放在应用程序内部文件夹,区别是在SD卡的任意目录存放时内容不会随应用程序的卸载而消失,而在应用程序内部的内容会随应用程序卸载消失。
一般缓存文件放在应用程序内部,用户主动保存的文件放在SD卡上的文件夹里。如果在SD卡上任意新建目录存放所有数据,用户卸载时会残存大量文件,招致用户反感。

Android存储及getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir()区别

  • Android中内部存储,外部存储的概念 ;
  • getDataDirectory,getFilesDir,getCacheDir,getDir,getExternalStorageDirectory,getExternalStoragePublicDirectory,getExternalFilesDir,getExternalCacheDir,getExternalCacheDir,getRootDirectory等方法区别联系 。
  • 清除数据和清除缓存到底清除了什么数据 ;
  • /storage/sdcard,/sdcard,/mnt/sdcard,/storage/emulated/0之间的关系 ;
  • 一张图看懂Ram,Rom,以及扩展存储(TF卡)的区别;内部存储,外部存储的区别。

记事本 - 图26

lib目录
这个子目录存放着的是应用程序需要使用的Native原生程序,即JNI调用需要加载的.so文件。
这些文件一般是位于apk安装包里的lib目录下。在程序安装的时候,系统会将所有apk文件中lib目录下的所有原生程序拷贝到这个子目录下。
该目录是程序不能操作的,不能在程序运行时,向该目录拷贝.so文件,然后再通过调用System.loadLibrary函数将其加载进来。

将InputStream写入本地文件

  1. /**
  2. * 将InputStream写入本地文件
  3. * @param destination 写入本地目录
  4. * @param input 输入流
  5. * @throws IOException
  6. */
  7. private static void writeToLocal(String destination, InputStream input)
  8. throws IOException {
  9. int index;
  10. byte[] bytes = new byte[1024];
  11. FileOutputStream downloadFile = new FileOutputStream(destination);
  12. while ((index = input.read(bytes)) != -1) {
  13. downloadFile.write(bytes, 0, index);
  14. downloadFile.flush();
  15. }
  16. downloadFile.close();
  17. input.close();
  18. }

Android : How to read file in bytes?

  1. File file = new File(path);
  2. int size = (int) file.length();
  3. byte[] bytes = new byte[size];
  4. try {
  5. BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
  6. buf.read(bytes, 0, bytes.length);
  7. buf.close();
  8. } catch (FileNotFoundException e) {
  9. // TODO Auto-generated catch block
  10. e.printStackTrace();
  11. } catch (IOException e) {
  12. // TODO Auto-generated catch block
  13. e.printStackTrace();
  14. }

AndroidStudio修改文件的默认打开方式

记事本 - 图27
在Settings→Editor→FileTypes中找到你文件的打开方式,然后把该打开方式下的文件名删掉

how to convert byte[] to Byte[], and the other way around?

  1. byte[] bytes = new byte[10];
  2. Byte[] byteObjects = new Byte[bytes.length];
  3. int i=0;
  4. // Associating Byte array values with bytes. (byte[] to Byte[])
  5. for(byte b: bytes)
  6. byteObjects[i++] = b; // Autoboxing.
  7. ....
  8. int j=0;
  9. // Unboxing Byte values. (Byte[] to byte[])
  10. for(Byte b: byteObjects)
  11. bytes[j++] = b.byteValue();

Bmob踩坑集合

1、implementation ‘cn.bmob.android:bmob-sdk:3.7.4’不成功
现象:no implementation
原因:未知
解决方法:导入本地SDK
2、No implementation found for boolean cn.bmob.v3.helper.BmobNative.init(androi
现象:导入本地SDK,编译后,APP闪退,报No implementation found for boolean cn.bmob.v3.helper.BmobNative.init(androi
原因:so镜像未导入
解决方法:从demo中手动导入so镜像
3、Lorg/reactivestreams/Publisher
现象:点击一个包含bmob控件的activity,闪退,报Lorg/reactivestreams/Publisher
原因:缺少reactive-streams.jar包
解决方式:implementation “org.reactivestreams:reactive-streams:1.0.2”
4、Failed resolution of: Lokhttp3/MediaType
现象:点击一个包含bmob控件的activity,闪退,报Failed resolution of: Lokhttp3/MediaType
原因:https://stackoverflow.com/questions/35627123/java-lang-noclassdeffounderror-failed-resolution-of-lokhttp3-mediatype
不能直接把okio这个jar包直接本地添加。
解决方法:删掉包,implementation ‘com.squareup.okhttp3:okhttp:3.12.1’

Android客户端与服务器端数据同步

应用场景
假设我们在做一个通讯录软件,我们可以在多个客户端对服务端的数据进行增删改。那么这篇文章中我们要解决的问题是如何在客户端与服务端只传输经过增删改操作的数据,来使得客户端与服务端的数据是同步的。

名词解释
Anchor:同步锚点,用时间戳来表示,用来发现两端数据变化的部分

客户端表设计
每条记录包含两个用来同步用的字段:
status : 用来标识记录的状态
anchor : 记录服务端同步过来的时间戳

anchor 含义
0 本地新增
-1 标记删除
1 本地更新
9 已同步

服务端表设计
modified : 服务端修改记录的时间戳

双向同步过程
初始状态下,我们假设客户端和服务端的表各有两条数据
客户端:

id name phone status anchor
1 Ken 18612345678 9 2
2 Jim 13888888888 9 3

服务端:

id name phone modified
1 Ken 18612345678 2
2 Jim 13888888888 3

此时,客户端与服务端的数据是完全同步好了的

Client增加1条记录

id name phone status anchor
1 Ken 18612345678 9 2
2 Jim 13888888888 9 3
3 Tim 12345678 0 0

Client修改1条记录

id name phone status anchor
1 Ken 18612345678 9 2
2 Jim 010-12345678 1 3
3 Tim 12345678 0 0

注:因为上面的数据不是从服务端同步过来的,所以anchor默认为0

Client发送本地更新
SELECT * FROM table WHERE status < 9

客户端执行上面的SQL语句,找出客户端需要同步到服务端的记录。通过网络串行的发送给服务端。上一条请求没有得到回应的话,就不能进行下一个请求。
下表中的数据是发送的同步消息

id name phone status anchor
2 Jim 010-12345678 1 3
3 Tim 12345678 0 0

Server处理同步消息
服务端串行的收到客户端发送过来的数据,首先处理第一条数据

id name phone status anchor
2 Jim 010-12345678 1 3

服务端收到请求后需要对比客户端的anchor和服务端的modified,只有服务端modified=客户端anchor才能继续同步,否则说明客户端在上一次同步后服务端更新过数据,需要解决冲突后才能继续,接着根据status的值为1,那么服务端执行UPDATE语句

UPDATE table SET (name, phone) VALUES (?, ?) WHERE id = ?

其次,处理第二条数据

id name phone status anchor
3 Tim 12345678 0 0

如果得知anchor = 0,直接执行INSERT 语句

INSERT INTO table (id, name, phone) VALUES(?, ?, ?)

服务端经过这两次操作后,数据表如下

id name phone modified
1 Ken 18612345678 2
2 Jim 010-12345678 6
3 Tim 12345678 8

**Client根据响应更新本地记录服务端处理完数据后,还要响应客户端的请求,如下

id status anchor
2 9 6
3 9 8

收到响应后,客户端就开始执行UPDATE了

  1. UPDATE table SET status = ?, anchor = ? WHERE id = ?• 1

客户端现在的数据表如下:
id name phone status anchor
1 Ken 18612345678 9 2
2 Jim 010-12345678 9 3->6
3 Tim 12345678 9 0->8
Server增加一条数据并更新一条数据
id name phone modified
1 Ken 00000000 11
2 Jim 010-12345678 6
3 Tim 12345678 8
4 Bill 88888888 10**

Client向Server请求数据
因为服务端modified字段代表的是时间戳,所以Max(anchor)表示客户端最近一次同步的时间,如果存在服务端modified > Max(anchor),说明服务端需要向客户端同步数据。
服务器端执行下面的SQL语句:

SELECT * FROM table WHERE modified > Max(anchor)
1
返回下表中的数据:

id name phone modified
1 Ken 00000000 11
4 Bill 88888888 10

Client处理同步消息
客户端根据增量数据更新本地表,处理数据时,只能更新状态为已同步或者不存在的数据

id name phone status anchor
1 Ken 00000000 9 2->11
2 Jim 010-12345678 9 6
3 Tim 12345678 9 8
4 Bill 88888888 9 10

客户端删除记录
逻辑删除记录
id name phone status anchor
1 Ken 00000000 -1 15
2 Jim 010-12345678 9 6
3 Tim 12345678 9 8
4 Bill 88888888 9 10

客户端发送消息到服务端
根据status < 9,将逻辑删除的记录发送至服务端,服务端收到消息后,将该记录移至deleted_table(相当于时光机,以后可以进行数据的恢复)表中

id name phone modified
1 Ken 00000000 16

服务端响应客户端的请求

id status anchor
1 -1 16

客户端收到响应
客户端直接进行物理删除

服务端删除记录
如果客户端从服务端获取的增量信息中包含删除记录的消息,则客户端直接进行物理删除

【Android】WebDav For Android

同时注意安卓9.0以上的网络安全策略,需要单独配置一下:
在res新建一个 network_config.xml。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <network-security-config>
  3. <base-config cleartextTrafficPermitted="true" />
  4. <domain-config cleartextTrafficPermitted="true">
  5. <domain includeSubdomains="true">www.pgyer.com</domain>
  6. <domain includeSubdomains="true">app-global.pgyer.com</domain>
  7. </domain-config>
  8. </network-security-config>

接着在Manifests的application中配置:

android:networkSecurityConfig=”@xml/network__config”
】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】】
implementation ‘com.thegrizzlylabs.sardine-android:sardine-android:0.5’

2.基础使用方法

请注意:以下所有方法都必须在新线程中进行,且对UI的直接操作要放回主线程进行,可以考虑使用Handler

(1)初始化

  1. Sardine sardine= new OkHttpSardine();
  2. sardine.setCredentials(userName,password);

这里的userName,password即你的用户账号+分配的应用的所属密码

(2)获取目录下所有文件

  1. List<DavResource> resources = null;
  2. try {
  3. resources = sardine.list("https://dav.jianguoyun.com/dav/");//如果是目录一定别忘记在后面加上一个斜杠
  4. for (DavResource res : resources)
  5. {
  6. listNames=listNames+res+"\n";
  7. }
  8. } catch (IOException e) {
  9. e.printStackTrace();
  10. }

(3)判断路径(或文件)是否存在

  1. //判断文件是否存在
  2. if (sardine.exists("https://dav.jianguoyun.com/dav/file1.jpg"))
  3. System.out.println("got here!");
  4. //判断路径是否存在
  5. if (sardine.exists("https://dav.jianguoyun.com/dav/file/"))
  6. System.out.println("got here!");


若目录不存在,可以自己创建:

sardine.createDirectory(“https://dav.jianguoyun.com/dav/ 目录名称/“);

(4)下载文件

InputStream is = sardine.get(“https://dav.jianguoyun.com/dav/"+"文件目录"+"afile.jpg“);

(5)上传文件

byte[] data = FileUtils.readFileToByteArray(new File(“/file/on/disk”)); sardine.put(“http://yourdavserver.com/adirectory/nameOfFile.jpg“, data);

或者使用

InputStream fis = new FileInputStream(new File(“/some/file/on/disk.txt”)); sardine.put(“http://yourdavserver.com/adirectory/nameOfFile.jpg“, fis);
节省内存空间

(6)删除文件

sardine.delete(“http://yourdavserver.com/adirectory/nameOfFile.jpg“);

jdk7之前,匿名内部类访问局部变量加final修饰的问题(综合两种说法)

When you create an instance of an anonymous inner class, any variables which are used within that class have their values copied in via the autogenerated constructor. This avoids the compiler having to autogenerate various extra types to hold the logical state of the “local variables”, as for example the C# compiler does… (When C# captures a variable in an anonymous function, it really captures the variable - the closure can update the variable in a way which is seen by the main body of the method, and vice versa.)
As the value has been copied into the instance of the anonymous inner class, it would look odd if the variable could be modified by the rest of the method - you could have code which appeared to be working with an out-of-date variable (because that’s effectively what would be happening… you’d be working with a copy taken at a different time). Likewise if you could make changes within the anonymous inner class, developers might expect those changes to be visible within the body of the enclosing method.
Making the variable final removes all these possibilities - as the value can’t be changed at all, you don’t need to worry about whether such changes will be visible. The only ways to allow the method and the anonymous inner class see each other’s changes is to use a mutable type of some description. This could be the enclosing class itself, an array, a mutable wrapper type… anything like that. Basically it’s a bit like communicating between one method and another: changes made to the parameters of one method aren’t seen by its caller, but changes made to the objects referred to by the parameters are seen.
If you’re interested in a more detailed comparison between Java and C# closures, I have an article which goes into it further. I wanted to focus on the Java side in this answer :)

Android PopupWindow与软键盘的遮挡问题

设置 popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

SOFT_INPUT_ADJUST_RESIZE: 整个Layout重新编排,重新分配多余空间

我们在弹出popupwindow时也希望像dialog一样窗口其他部分变暗,我们可以获取窗口属性设置其ALPHA值:
1
WindowManager.LayoutParams params = getWindow().getAttributes();
params.alpha = 0.3f;
getWindow().setAttributes(params);

在popupwindow隐藏时,窗体再恢复原来亮度,可以设置popupwindow的隐藏监听恢复窗体亮度:

mPopWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.alpha = 1f;
getWindow().setAttributes(params);
}
});

软键盘与PopupWindow的冲突

问题一:设置activity的SoftInputMode无效

在需求中,PopupWindow的布局需要EditText的layout居于底部,软键盘弹出时候要把EditText往上顶,但是布局整体不会往上走,然后大家都知道使用adjustResize属性,但是这个属于要对应到相应的Window上才会生效的。

知识点补充

在一个Activity上如何统计有多少个Window,大家都知道View的层级关系是Window -> DecorView,这个在Android艺术开发探索中有详细讲解,大家不明白的可以去查下。回归正题,如何统计一个Activity上的Window数量。
总数: window_all_number = iphoneWindow + 全部的PopupWindow的Window + 全部的Dialog的Window。
所以说各自的Window会管理各自的布局变化,上面我是想修改PopupWindow布局的ajustResize属性,但是却去修改了activity上的ajustResize属性,很显然这样子做popupWindow上是没有ajustResize属性,所以自然达不到要求的效果。

问题二:设置PopupWindow的ajustResize属性后,EditText的Layout一直在底部,不跟随软键盘的弹出往上顶,就是软键盘挡住了PopupWindow的底部布局

失败尝试方法1

因为我的activity的SoftInputMode属性是SOFT_INPUT_ADJUST_NOTHING,然后我强行在PopupWindow显示时候设置成SOFT_INPUT_ADJUST_RESIZE

失败尝试方法2

改变PopupWindow的初始化
失败原因:PopupWindow初始化与设置ajustResize属性没太大关系,设置ajustResize属性是当软键盘弹出时候,系统会缩小当前布局的高度,从而适应软键盘的弹出。在失败两次之后,我觉得把目光锁定在了PopupWindow的宽高设置上。

问题二的解决办法

上面也分析说了将目光锁定在PopupWindow的宽高设置上,先看下代码中的宽高如何设置的。
  1. mPopupWindow.setWidth(WindowManagerHelper.getDisplayWidth(mContentView.getContext()));
  2. mPopupWindow.setHeight(WindowManagerHelper.getDisplayHeight(mContentView.getContext()));

代码中的mContentView.getContext是属于Activity的Context,因为你的mContentView是通过LayoutInflater.from(context).inflate去生成的。所以你可以想下,当你的PopupWindow的ajustResize起作用时候,是不是当前布局改变由Window控制,但是上面代码这样设置后,是将PopupWindow的大小改变选择权交给了Activity的Window去控制,所以当你设置ajustResize后,软键盘弹出,布局大小没有改变,因为PopupWindow的Window虽然接收通知,但是却改变大小权利不在它手上。

如何修改
  • 改变PopupWindow初始化值
  • 将PopupWindow的大小改变权限交换给PopupWindow的Window

软键盘弹出时将某个控件顶出,搞乱布局

解决办法:
方法一:在你的activity中的oncreate中setContentView之前写上这个代码getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

方法二:在项目的AndroidManifest.xml文件中界面对应的里加入android:windowSoftInputMode=”stateVisible|adjustResize”,这样会让屏幕整体上移。如果加上的是android:windowSoftInputMode=”adjustPan”这样键盘就会覆盖屏幕。

方法三:把顶级的layout替换成ScrollView,或者说在顶级的Layout上面再加一层ScrollView的封装。这样就会把软键盘和输入框一起滚动了,软键盘会一直处于底部。

主窗口与软键盘窗口交互设置值如下:

描述
“stateUnspecified” 软键盘的状态(是否它是隐藏或可见)没有被指定。系统将选择一个合适的状态或依赖于主题的设置。
这个是为了软件盘行为默认的设置。
“stateUnchanged” 软键盘被保持无论它上次是什么状态,是否可见或隐藏,当主窗口出现在前面时。
“stateHidden” 当用户选择该Activity时,软键盘被隐藏——也就是,当用户确定导航到该Activity时,而不是返回到它由于离开另一个Activity。
“stateAlwaysHidden” 软键盘总是被隐藏的,当该Activity主窗口获取焦点时。
“stateVisible” 软键盘是可见的,当那个是正常合适的时(当用户导航到Activity主窗口时)。
“stateAlwaysVisible” 当用户选择这个Activity时,软键盘是可见的——也就是,也就是,当用户确定导航到该Activity时,而不是返回到它由于离开另一个Activity。
“adjustUnspecified” 它不被指定是否该Activity主窗口调整大小以便留出软键盘的空间,或是否窗口上的内容得到屏幕上当前的焦点是可见的。系统将自动选择这些模式中一种主要依赖于是否窗口的内容有任何布局视图能够滚动他们的内容。如果有这样的一个视图,这个窗口将调整大小,这样的假设可以使滚动窗口的内容在一个较小的区域中可见的。这个是主窗口默认的行为设置。
“adjustResize” 该Activity主窗口总是被调整屏幕的大小以便留出软键盘的空间
“adjustPan” 该Activity主窗口并不调整屏幕的大小以便留出软键盘的空间。相反,当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分。这个通常是不期望比调整大小,因为用户可能关闭软键盘以便获得与被覆盖内容的交互操作。

解决Android软键盘在全屏下设置adjustResize无效的问题

输入法遮挡问题

解决输入法遮挡的问题 基本上有两种

  • adjustResize + ScrollView
  • adjustPan

adjustPan会把页面整体上推
adjustResize则是缩放可调整页面 所以要和ScrollView配合 但是如果界面设成全屏模式就不会生效

解决方式

非全屏模式(即状态栏不透明)下,将activitywindowSoftInputMode的属性设置为:adjustResize。同时在ViewonSizeChanged(int w, int h, int oldw, int oldh)里可以得到变化后的尺寸,然后根据前后变化的结果来计算屏幕需要移动的距离。
即添加:
android:windowSoftInputMode=”adjustResize”
但是在全屏模式下,即使将activitywindowSoftInputMode的属性设置为:adjustResize
在键盘显示时它未将Activity的Screen向上推动,所以你Activityview的根树的尺寸是没有变化的。
在这种情况下,你也就无法得知键盘的尺寸,对根view的作相应的推移。 全屏下的键盘无法Resize的问题从2.1就已经存在了,直到现在google还未给予解决。
有人已经封装好了该类,你只需引用就OK了,我们来看下这个类。

  1. public class SoftHideKeyBoardUtil {
  2. public static void assistActivity (Activity activity) {
  3. new SoftHideKeyBoardUtil(activity);
  4. }
  5. private View mChildOfContent;
  6. private int usableHeightPrevious;
  7. private FrameLayout.LayoutParams frameLayoutParams;
  8. //为适应华为小米等手机键盘上方出现黑条或不适配
  9. private int contentHeight;//获取setContentView本来view的高度
  10. private boolean isfirst = true;//只用获取一次
  11. private int statusBarHeight;//状态栏高度
  12. private SoftHideKeyBoardUtil(Activity activity) {
  13. //1、找到Activity的最外层布局控件,它其实是一个DecorView,它所用的控件就是FrameLayout
  14. FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
  15. //2、获取到setContentView放进去的View
  16. mChildOfContent = content.getChildAt(0);
  17. //3、给Activity的xml布局设置View树监听,当布局有变化,如键盘弹出或收起时,都会回调此监听
  18. mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  19. //4、软键盘弹起会使GlobalLayout发生变化
  20. public void onGlobalLayout() {
  21. if (isfirst) {
  22. contentHeight = mChildOfContent.getHeight();//兼容华为等机型
  23. isfirst = false;
  24. }
  25. //5、当前布局发生变化时,对Activity的xml布局进行重绘
  26. possiblyResizeChildOfContent();
  27. }
  28. });
  29. //6、获取到Activity的xml布局的放置参数
  30. frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
  31. }
  32. // 获取界面可用高度,如果软键盘弹起后,Activity的xml布局可用高度需要减去键盘高度
  33. private void possiblyResizeChildOfContent() {
  34. //1、获取当前界面可用高度,键盘弹起后,当前界面可用布局会减少键盘的高度
  35. int usableHeightNow = computeUsableHeight();
  36. //2、如果当前可用高度和原始值不一样
  37. if (usableHeightNow != usableHeightPrevious) {
  38. //3、获取Activity中xml中布局在当前界面显示的高度
  39. int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
  40. //4、Activity中xml布局的高度-当前可用高度
  41. int heightDifference = usableHeightSansKeyboard - usableHeightNow;
  42. //5、高度差大于屏幕1/4时,说明键盘弹出
  43. if (heightDifference > (usableHeightSansKeyboard/4)) {
  44. // 6、键盘弹出了,Activity的xml布局高度应当减去键盘高度
  45. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
  46. frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
  47. } else {
  48. frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
  49. }
  50. } else {
  51. frameLayoutParams.height = contentHeight;
  52. }
  53. //7、 重绘Activity的xml布局
  54. mChildOfContent.requestLayout();
  55. usableHeightPrevious = usableHeightNow;
  56. }
  57. }
  58. private int computeUsableHeight() {
  59. Rect r = new Rect();
  60. mChildOfContent.getWindowVisibleDisplayFrame(r);
  61. // 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度
  62. return (r.bottom - r.top);
  63. }
  64. }

使用方法

在你的ActivityonCreate()方法里调用即可
SoftHideKeyBoardUtil.assistActivity(this);

注意:在setContentView(R.layout.xxx)之后调用

PopupWindow之踩坑(1) setFocusable与setOutsideTouchable问题

是否正确理解setFocusable(boolean focusable)setOutsideTouchable(boolean touchable)

在5.0以下的系统,当你没有setBackgroundDrawable时,此时的popup是完全没有处理你的屏幕点击触摸事件的能力的,包括你的物理返回按键也一样不会响应,这时候不管你去如何设置setOutsideTouchable都是没有意义的,根本不会进入到onTouchEvent方法里面,也就走不到if (event.getAction() == MotionEvent.ACTION_OUTSIDE)这个逻辑判断了。
这个MotionEvent.ACTION_OUTSIDE很熟悉,就是通过这个action来判断是否响应外侧点击事件的,那么它是怎么触发的呢?
当把 MotionEvent.ACTION_OUTSIDEsetOutsideTouchable(boolean touchable)放到一起,就会很明了了。

现在也就很好明白为什么在设置了setFoucus(true)后再去设置 setOutsideTouchable(false)为什么没有作用了,因为MotionEvent.ACTION_DOWN事件在MotionEvent.ACTION_OUTSIDE事件之前处理,消耗了MotionEvent事件。这样也就能解释官方对setOutsideTouchable方法的说明了)

“控制是否通知popup窗体外的点击事件,这个方法只有在touchable为true而focusable为false的时候才有意义”, 说的很严谨,是有没有意义而不是有没有作用。

从源码剖析PopupWindow 兼容Android 6.0以上版本点击外部不消失

edittext监听图片的点击事件

设置图片点击事件的主要代码
分两种情况
第一种是该EditText已经获得了焦点
使用
et.setOnClickListener()监听

第二种是该EditText没有焦点 则在
et.setOnFocusChangedListener()中监听

监听的内容都是一致的
Spanned s = et.getText();//得到Spanned对象

ImageSpan[] imagespans = s.getSpans(0, s.length(), ImageSpan.class); //得到该EditText中多有的ImageSpan对象

int selectStart = et.getSelectionStart(); //获得当前EditText中的光标位置

//遍历所有的ImageSpan 根据光标位置判断点击的是哪一个ImageSpan
for (ImageSpan span : imagespans) {

  1. int start = s.getSpanStart(span);<br /> int end = s.getSpanEnd(span);<br /> Log.i("info", "start:" + start + ",end:" + end);<br /> if (selectStart >= start && selectStart <= end) {<br /> Toast.makeText(MainActivity.this, "点击了图片",<br /> Toast.LENGTH_LONG).show();<br /> }<br /> }

仿小米便签图文混排 EditText解决尾部插入文字bug

一直想实现像小米便签那样的图文混排效果,收集网上的办法无非三种:
1、自定义布局,每张图片是一个ImageView,插入图片后插入EditText,缺点是实现复杂,不能像小米便签那样同时选中图片和文字
2、通过Html.fromHtml(source),可以将图片加载写进ImageGetter,实现后无bug,但是只能显示Html,当EditText setText后,想取出之前的HTML格式
图片得到的是一个obj的字符,查看了很多博客,包括stackoverflow也没给出办法从editable中解析出spanned对象。若谁有方法希望不吝啬告诉我。
3、通过ImageSpan和SpannableString,这是我实现的方法,而且较为理想,不但可以写入EditText,也可以从EditText中解析出图文混排排版。

  1. /**
  2. * 从当前的EditText获取ImageSpan,如果存在则返回否则返回Null
  3. *
  4. * @return
  5. */
  6. private ImageSpan getImageSpanFromExistence(String source) {
  7. Editable edit = contentText.getText();
  8. ImageSpan[] spans = edit.getSpans(0, edit.length(), ImageSpan.class);
  9. for (ImageSpan ip : spans) {
  10. int start = edit.getSpanStart(ip);
  11. int end = edit.getSpanEnd(ip);
  12. String path = edit.toString().substring(start, end);
  13. path = path.substring(5, path.length() - 5);
  14. if (source.equals(path)) {
  15. Logg.D("find existed ImageSpan");
  16. return new ImageSpan(ip.getDrawable(), ImageSpan.ALIGN_BASELINE);
  17. }
  18. }
  19. return null;
  20. }

使用Android自带的TTS实现语音播报(电话号码)功能

注:TTS读语音数字的时候需要用空格隔开,如150需要写成1 5 0,读“一五零”,否则TTS会读成“一百五十”

保存二进制数据

SharedPreferences原则上只能将字符串以key-value的形式保存, 但是万物皆二进制,所以我们可以采用编码的方式将任何二进制数据转化为字符串, 从而将可以将二进制数据保存在SharedPreferences文件中,而最常用的编码格式是Base64.

  1. /**
  2. * @param context
  3. * @param preferenceName
  4. * @param resId
  5. * @param key
  6. */
  7. public static void saveDrawable(Context context, String preferenceName,int resId,String key) {
  8. SharedPreferences sharedPreferences=context.getSharedPreferences(preferenceName,context.MODE_PRIVATE);
  9. SharedPreferences.Editor editor=sharedPreferences.edit();
  10. Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId);
  11. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  12. bitmap.compress(Bitmap.CompressFormat.PNG, 50, baos);
  13. String imageBase64 = new String(Base64.encodeToString(baos.toByteArray(),Base64.DEFAULT));
  14. editor.putString(key,imageBase64 );
  15. editor.commit();
  16. }

读取二进制数据

  1. public static Drawable getDrawableByKey(Context context, String preferenceName,String key) {
  2. SharedPreferences sharedPreferences=context.getSharedPreferences(preferenceName,context.MODE_PRIVATE);
  3. String temp = sharedPreferences.getString(key, "");
  4. ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(temp.getBytes(), Base64.DEFAULT));
  5. return Drawable.createFromStream(bais, "");
  6. }

由于二进制数据经过编码后可以用SharedPreferences以字符串的形式存储,所以保存对象也成为了可能,但是这个类必须是可序列化即implements Serializable(实际上Serializable接口是个空接口,只是为了标记该对象是被序列化的),然后可以通过ObjectOutputStream保存再转为二进制存储

用ClipDrawable实现音频录制麦克风讲话效果

由于最近项目开发需要用到自定义SeekBar,于是又对android下的各种类型drawable进行了一个全面系统的认识,只能感慨drawable的功能还是很强大的。通过自定义SeekBar有感而发,尝试用ClipDrawable实现音频录制过程的一个麦克风录制效果:
在实现开发之前先让我们一起认识两种类型的Drawable:

LayerDrawable

LayerDrawable可以包含一个drawable数组,而且系统会根据drawable数组的前后顺序来绘制所有的drawable,索引最大的drawable也就相应的会被绘制在最上面。使用过PhotoShop的朋友应该会比较容易理解,LayerDrawable和PhotoShop中图层的概念很相似,这里drawable数组中的每一个drawable就相当于PhotoShop中的一个图层,上一个图层会遮住之后所有图层与之重叠的部分。

定义LayerDrawable对象的XML文件的根元素为,该元素可以包含对个元素也就是一个drawable对象。

ClipDrawable

ClipDrawable,顾名思义这就是一个可以进行裁切的drawable,在XML文件中定义ClipDrawable对象使用的根元素是元素,该元素包含以下几个重要的属性:

  • android:drawable:指定将要被截取的Drawable对象。
  • android:clipOrientation:指定Drawable对象的截取方向可以是水平和竖直方向。
  • android:gravity:表示Drawable对象的对齐方式,例如:left 可以理解为左边部分为保留部分,右边部分为剪切部分,则我们可以看到的就是截取的左边部分。

注意,使用ClipDrawable对象时可以调用setLevel(int level)方法来控制截取区域的大小,而level的取值区间在0~10000之间,则level为0时,表示图片截取部分为空,当了level为10000时,截取整张图片。
了解完毕,下面我们就要用这两种Drawable结合使用开发我们今天的麦克风说话效果:
首先,准备两张位图
记事本 - 图28
top drawable
记事本 - 图29
bottom drawable

然后,在XML中新建一个拥有两个Drawable的LayerDrawable文件layer-microphone.xml,在顶层显示的是可以裁切的ClipDrawable,设置剪切方向为竖直方向,设置对其方式为bottom,底部的则是不通的Drawable作为背景。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
  3. <item android:id="@android:id/background"
  4. android:drawable="@drawable/icon_microphone_normal" />
  5. <item android:id="@android:id/progress" >
  6. <clip android:drawable="@drawable/icon_microphone_recoding"
  7. android:gravity="bottom"
  8. android:clipOrientation="vertical" />
  9. </item>
  10. </layer-list>

然后,再自定义一个PopupWindow用于音频录制过程显示麦克风动画效果,关于自定义popupWindow有疑问的朋友可以参考我的上一篇文章“2016-05-10 浅谈PopupWindow在Android开发中的使用”

窗口布局如下layout_microphone.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:gravity="center"
  4. android:orientation="vertical"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent" >
  7. <LinearLayout
  8. android:background="@drawable/shape_window_background"
  9. android:layout_height="wrap_content"
  10. android:layout_width="wrap_content"
  11. android:orientation="vertical"
  12. android:gravity="center"
  13. android:padding="16dp" >
  14. <ImageView
  15. android:layout_width="48dp"
  16. android:layout_height="48dp"
  17. android:id="@android:id/progress"
  18. android:src="@drawable/layer-microphone" />
  19. <TextView
  20. android:layout_height="wrap_content"
  21. android:layout_marginTop="6dp"
  22. android:id="@android:id/text1"
  23. android:layout_width="114dp"
  24. android:textColor="#FFFFFF"
  25. android:gravity="center"
  26. android:textSize="16sp"
  27. android:text="00:00" />
  28. </LinearLayout>
  29. </LinearLayout>

从代码中可以看出,我把ImageVie的资源设为layer-microphone.xml,我们获取ImageView的Drawable对象并设置Level值就能实现想要的效果:

  1. imageView.getDrawable().setLevel(5400);

记事本 - 图30
icon_microphone_recoding11.png

从这里我们已经可以看到裁切效果。最后一步,我们只要能够实时获取音频录制过程中的分贝值的变化,再将分贝值变化转换到相应的Level值,就能实现音频录制说话效果啦,于是百度,在网上看到一篇文章“Android中实时获取音量分贝值详解” ,首先,感谢作者的分享,于是我也就照着方法写了一个AudioRecoderUtil.java类,稍加改进,添加了一个监听事件,代码如下:

  1. package com.mariostudio.audiorecoder;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import android.media.MediaRecorder;
  5. import android.os.Handler;
  6. import android.util.Log;
  7. /**
  8. * Created by MarioStudio on 2016/5/12.
  9. */
  10. public class AudioRecoderUtils {
  11. private String filePath;
  12. private MediaRecorder mMediaRecorder;
  13. private final String TAG = "MediaRecord";
  14. public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;
  15. private OnAudioStatusUpdateListener audioStatusUpdateListener;
  16. public AudioRecoderUtils(){
  17. this.filePath = "/dev/null";
  18. }
  19. public AudioRecoderUtils(File file) {
  20. this.filePath = file.getAbsolutePath();
  21. }
  22. private long startTime;
  23. private long endTime;
  24. /**
  25. * 开始录音 使用amr格式
  26. * 录音文件
  27. * @return
  28. */
  29. public void startRecord() {
  30. // 开始录音
  31. /* ①Initial:实例化MediaRecorder对象 */
  32. if (mMediaRecorder == null)
  33. mMediaRecorder = new MediaRecorder();
  34. try {
  35. /* ②setAudioSource/setVedioSource */
  36. mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
  37. /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
  38. mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
  39. /*
  40. * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
  41. * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
  42. */
  43. mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  44. /* ③准备 */
  45. mMediaRecorder.setOutputFile(filePath);
  46. mMediaRecorder.setMaxDuration(MAX_LENGTH);
  47. mMediaRecorder.prepare();
  48. /* ④开始 */
  49. mMediaRecorder.start();
  50. // AudioRecord audioRecord.
  51. /* 获取开始时间* */
  52. startTime = System.currentTimeMillis();
  53. updateMicStatus();
  54. Log.i("ACTION_START", "startTime" + startTime);
  55. } catch (IllegalStateException e) {
  56. Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
  57. } catch (IOException e) {
  58. Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
  59. }
  60. }
  61. /**
  62. * 停止录音
  63. */
  64. public long stopRecord() {
  65. if (mMediaRecorder == null)
  66. return 0L;
  67. endTime = System.currentTimeMillis();
  68. Log.i("ACTION_END", "endTime" + endTime);
  69. mMediaRecorder.stop();
  70. mMediaRecorder.reset();
  71. mMediaRecorder.release();
  72. mMediaRecorder = null;
  73. Log.i("ACTION_LENGTH", "Time" + (endTime - startTime));
  74. return endTime - startTime;
  75. }
  76. private final Handler mHandler = new Handler();
  77. private Runnable mUpdateMicStatusTimer = new Runnable() {
  78. public void run() {
  79. updateMicStatus();
  80. }
  81. };
  82. /**
  83. * 更新话筒状态
  84. */
  85. private int BASE = 1;
  86. private int SPACE = 100;// 间隔取样时间
  87. public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
  88. this.audioStatusUpdateListener = audioStatusUpdateListener;
  89. }
  90. private void updateMicStatus() {
  91. if (mMediaRecorder != null) {
  92. double ratio = (double)mMediaRecorder.getMaxAmplitude() / BASE;
  93. double db = 0;// 分贝
  94. if (ratio > 1) {
  95. db = 20 * Math.log10(ratio);
  96. if(null != audioStatusUpdateListener) {
  97. audioStatusUpdateListener.onUpdate(db);
  98. }
  99. }
  100. mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
  101. }
  102. }
  103. public interface OnAudioStatusUpdateListener {
  104. public void onUpdate(double db);
  105. }
  106. }

进行到这一步,已经基本完成了这个效果,最后只需要在自定义PopupWindow的时候提供方法setLevel(int level)就可以轻松实现PopupWindow实时刷新分贝值啦!!

  1. public void setLevel(int level) {
  2. imageView.getDrawable().setLevel(3000 + 6000 * level / 100);
  3. }

记事本 - 图31
动态效果图
至于为什么设置level的时候要3000 + 6000 * level / 100以及计时效果,就都留给聪明的你去探索咯!!
Github项目Demo地址

Android实现长按录音松开保存、播放及根据声贝动画展示

ActionBarDrawerToggle的使用

 ActionBarDrawerToggle 是 DrawerLayout.DrawerListener实现。和 NavigationDrawer 搭配使用,推荐用这个方法,符合Android design规范。
  作用:
    1.改变android.R.id.home返回图标。
    2.Drawer拉出、隐藏,带有android.R.id.home动画效果。
    3.监听Drawer拉出、隐藏;
ActionBarDrawerToggle 是在actionBar监视DrawerLayout的状态变化
所以不要设置activity为无标题

  1. //requestWindowFeature(Window.FEATURE_NO_TITLE);

监视抽屉的打开和关闭

  1. /**
  2. * 内部实现抽屉状态监听
  3. * onDrawerOpen
  4. * onDrawerClose
  5. */
  6. mDrawerToggle = new ActionBarDrawerToggle(
  7. MainActivity.this,
  8. mDrawerLayout,
  9. R.drawable.ic_drawer,
  10. R.string.drawer_open,
  11. R.string.drawer_close){
  12. @Override
  13. public void onDrawerClosed(View drawerView) {
  14. super.onDrawerClosed(drawerView);
  15. getActionBar().setTitle(mTitle);
  16. invalidateOptionsMenu(); // creates call to
  17. // onPrepareOptionsMenu()
  18. }
  19. @Override
  20. public void onDrawerOpened(View drawerView) {
  21. super.onDrawerOpened(drawerView);
  22. getActionBar().setTitle(mDrawerTitle);
  23. invalidateOptionsMenu(); // creates call to
  24. // onPrepareOptionsMenu()
  25. }
  26. };
  27. //设置抽屉状态监听事件
  28. mDrawerLayout.addDrawerListener(mDrawerToggle);
  29. mDrawerToggle.syncState();
  30. }

Navigation Drawer - what does syncState() do and why it should be called inside onPostCreate()?

https://developer.android.com/reference/android/support/v4/app/ActionBarDrawerToggle#syncstate

Android入门—弹出三个按钮的对话框

  1. package com.example.forwarding;
  2. import android.support.v7.app.ActionBarActivity;
  3. import android.app.AlertDialog;
  4. import android.content.DialogInterface;
  5. import android.os.Bundle;
  6. import android.view.View;
  7. import android.widget.Button;
  8. import android.widget.TextView;
  9. public class Forwarding extends ActionBarActivity {
  10. @Override
  11. public void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_forwarding);
  14. final Button dialogButton = (Button) findViewById(R.id.button1);
  15. final TextView texts = (TextView) findViewById(R.id.textView1);
  16. dialogButton.setOnClickListener(new View.OnClickListener() {// 按键单击事件
  17. @Override
  18. public void onClick(View v) {
  19. // TODO Auto-generated method stub
  20. new AlertDialog.Builder(Forwarding.this)
  21. .setTitle("系统提示")
  22. // 设置对话框标题
  23. .setMessage("请确认所有数据都保存后再推出系统!")
  24. // 设置显示的内容
  25. //右边按钮
  26. .setPositiveButton("确定",
  27. new DialogInterface.OnClickListener() {// 添加确定按钮
  28. @Override
  29. public void onClick(
  30. DialogInterface dialog,
  31. int which) {// 确定按钮的响应事件
  32. // TODO Auto-generated method
  33. // stub
  34. //finish();
  35. texts.setText("结果:确认 ");
  36. }
  37. })
  38. //中间按钮
  39. .setNeutralButton("测试", new DialogInterface.OnClickListener() {
  40. public void onClick(DialogInterface dialog, int whichButton) {
  41. texts.setText("结果:测试 ");
  42. }
  43. })
  44. //左边按钮
  45. .setNegativeButton("返回",
  46. new DialogInterface.OnClickListener() {// 添加返回按钮
  47. @Override
  48. public void onClick(
  49. DialogInterface dialog,
  50. int which) {// 响应事件
  51. // TODO Auto-generated method
  52. // stub
  53. //Log.i("alertdialog", " 请保存数据!");
  54. texts.setText("结果:返回 ");
  55. }
  56. }).show();// 在按键响应事件中显示此对话框
  57. }
  58. });
  59. }
  60. }

Android 获取栈顶activity

在自己的 Application 中去记录activity,在service 中可以通过 MyApplication.getInstance().getCurrentActivity()去调用获取当前最上面activity

  1. package com.example.note.application;
  2. import android.app.Activity;
  3. import android.app.Application;
  4. import android.content.Context;
  5. import android.os.Bundle;
  6. import android.util.Log;
  7. import androidx.annotation.NonNull;
  8. import androidx.annotation.Nullable;
  9. import com.example.note.MainActivity;
  10. import org.litepal.LitePal;
  11. public class MyApplication extends Application {
  12. private static final String TAG = "MyApplication";
  13. private static MyApplication mInstance;
  14. private Activity mActivity;
  15. private Context context;
  16. @Override
  17. public void onCreate() {
  18. Log.d(TAG, "onCreate: ");
  19. super.onCreate();
  20. context = getApplicationContext();
  21. mInstance = this;
  22. LitePal.initialize(getApplicationContext());
  23. registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
  24. @Override
  25. public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
  26. // 此处记录最后的activity
  27. Log.d(TAG, "onActivityCreated: " );
  28. }
  29. @Override
  30. public void onActivityStarted(@NonNull Activity activity) {
  31. Log.d(TAG, "onActivityStarted: " );
  32. }
  33. @Override
  34. public void onActivityResumed(@NonNull Activity activity) {
  35. mActivity = activity;
  36. Log.d(TAG, "onActivityResumed: "+ (activity instanceof MainActivity));
  37. }
  38. @Override
  39. public void onActivityPaused(@NonNull Activity activity) {
  40. mActivity = null;
  41. Log.d(TAG, "onActivityPaused: ");
  42. }
  43. @Override
  44. public void onActivityStopped(@NonNull Activity activity) {
  45. Log.d(TAG, "onActivityStopped: ");
  46. }
  47. @Override
  48. public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
  49. Log.d(TAG, "onActivitySaveInstanceState: ");
  50. }
  51. @Override
  52. public void onActivityDestroyed(@NonNull Activity activity) {
  53. Log.d(TAG, "onActivityDestroyed: ");
  54. }
  55. });
  56. }
  57. public Activity getCurrentActivity() {
  58. return mActivity;
  59. }
  60. public Context getContext() {
  61. return context;
  62. }
  63. public static MyApplication getInstance() {
  64. return mInstance;
  65. }
  66. }

Android 应用内获取当前运行Activity

  • 反射解决方案 ```java /**
    • 获取当前运行的activity */ public static Activity getTopActivity() { Log.i(“activity”, “[getTopActivity]”); try {
      1. Class activityThreadClass = Class.forName("android.app.ActivityThread");
      2. Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null);
      3. Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
      4. activitiesField.setAccessible(true);
      5. //16~18 HashMap
      6. //19~27 ArrayMap
      7. Map<Object, Object> activities;
      8. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
      9. activities = (HashMap<Object, Object>) activitiesField.get(activityThread);
      10. } else {
      11. activities = (ArrayMap<Object, Object>) activitiesField.get(activityThread);
      12. }
      13. if (activities.size() < 1) {
      14. return null;
      15. }
      16. for (Object activityRecord : activities.values()) {
      17. Class activityRecordClass = activityRecord.getClass();
      18. Field pausedField = activityRecordClass.getDeclaredField("paused");
      19. pausedField.setAccessible(true);
      20. if (!pausedField.getBoolean(activityRecord)) {
      21. Field activityField = activityRecordClass.getDeclaredField("activity");
      22. activityField.setAccessible(true);
      23. Activity activity = (Activity) activityField.get(activityRecord);
      24. return activity;
      25. }
      26. }
      } catch (Exception e) {
      1. e.printStackTrace();
      } return null; }
  1. 优先使用ActivityLifecycleCallbacks,在lifecycle中获取为空时,再通过反射进行二次获取(lifecycle极地概率获取不到,可能原因是国产romactivity生命周期出现异常导致)
  2. <a name="vRsgZ"></a>
  3. ## [利用反射拿到Android的整个Activity栈。](https://blog.csdn.net/dreamfly130/article/details/80803802?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task)
  4. <a name="GFT84"></a>
  5. # Application Context和Activity Context的区别
  6. 大家注意看到有一些NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:<br />数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。<br />数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。<br />数字3:在receivernull时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)<br />注:ContentProviderBroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。<br />![](https://cdn.nlark.com/yuque/0/2020/png/544184/1584773321218-7ed69e8c-fde7-476a-bd3b-baa1c874fde1.png#height=553&id=C9Bse&margin=%5Bobject%20Object%5D&originHeight=553&originWidth=1010&originalType=binary&ratio=1&size=0&status=done&style=none&width=1010)<br />好了,这里我们看下表格,重点看Activity和Application,可以看到,和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。<br />以上这段话是引用了鸿洋大神的博客,这么一看,我们也就很清楚了Application Context和Activity Context的区别。
  7. <a name="X7eT5"></a>
  8. ## 安卓页面跳转使用Context.startActivity注意事项
  9. Context中有一个startActivity方法,Activity继承自Context,重载了startActivity方法。如果使用 ActivitystartActivity方法,不会有任何限制,而如果使用ContextstartActivity方法的话,就需要开启一个新的task,遇到上面那个异常的,都是因为使用了ContextstartActivity方法。解决办法是,加一个flagintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  10. _**Intent**_.**_FLAG_ACTIVITY_NEW_TASK 设置状态,首先查找是否存在和被启动的Activity具有相同的任务栈,如果有则直接把这个栈整体移到前台,并保持栈中的状态不变,既栈中的activity顺序不变,如果没有,则新建一个栈来存放被启动的Activity_**
  11. <a name="xsKAF"></a>
  12. ### [context.startActivity(intent) 7.0以下与7.0及以上的区别](https://blog.csdn.net/qq_20230661/article/details/82424199?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task)
  13. <a name="65xxS"></a>
  14. ### [Context.startActivity() 与 Activity.startActivity() 究竟有什么不同?](https://blog.csdn.net/crazy1235/article/details/101393091?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task)
  15. <a name="SWyMP"></a>
  16. ### [Android P新特性:强制执行 FLAG_ACTIVITY_NEW_TASK 要求](https://blog.csdn.net/dzkdxyx/article/details/80022565?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task)
  17. <a name="zpkV2"></a>
  18. # [singleTop模式应用场景 以及OnNewIntent](https://blog.csdn.net/yuzhiyun3536/article/details/75665743)
  19. <a name="M6aTT"></a>
  20. # [Android-AlarmManager多个闹钟相互独立的实现](https://blog.csdn.net/laichao1112/article/details/6552302?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task)
  21. ```java
  22. Intent i=new Intent(TimeSetActivity.this,AlarmReceiver.class);
  23. PendingIntent pi = PendingIntent.getBroadcast(TimeSetActivity.this, Integer.valueOf(id) , i, 0); //通过getBroadcast第二个参数区分闹钟,将查询得到的note的ID值作为第二个参数。
  24. AlarmManager am = (AlarmManager) getSystemService(Activity.ALARM_SERVICE);
  25. am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pi);//设置闹铃
  26. Intent i=new Intent(TimeSetActivity.this,AlarmReceiver.class);
  27. PendingIntent pi = PendingIntent.getBroadcast(TimeSetActivity.this, Integer.valueOf(id) , i, 0);
  28. am.cancel(pi);//取消闹钟
  29. 这样就通过getBroadcast的第二个参数有效的区分了各个闹钟

PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags);
第二个参数requestCode一定要是唯一的,比如不同的ID之类的,(如果系统需要多个定时器的话)。

Android TextView内容过长加省略号,点击显示全部内容

在xml中:
android:ellipsize=”end” 省略号在结尾
android:ellipsize=”start”  省略号在开头
android:ellipsize=”middle” 省略号在中间
android:ellipsize=”marquee” 跑马灯
最好加一个TextView显示行数的约束,例如:
android:singleline=”true”或者android:lines=”2”

在java文件中:
tv.setEllipsize(TextUtils.TruncateAt.valueOf(“END”));
tv.setEllipsize(TextUtils.TruncateAt.valueOf(“START”));
tv.setEllipsize(TextUtils.TruncateAt.valueOf(“MIDDLE”));
tv.setEllipsize(TextUtils.TruncateAt.valueOf(“MARQUEE”));
最好加一个TextView显示行数的约束,例如:
tv.setSingleLine(true);

不仅对于textview有此属性,对于editext也有,不过它不支持marquee。
image.png

TextView maxWidth maxLength maxEms 区别

maxWidth=”80dp” 限制TextView最大宽度。必须与layout_width=”wrap_content”搭配使用,当指定layout_width为其他值时,maxWidth会失效。

maxLength=”10” 限制TextView最多10个字符数。汉字、英文、数字都算一个字符。maxLength属性会使ellipsize=”end”属性失效。添加了maxLength不再会显示”…”

maxEms=”5” 限制TextView的最大宽度为5个大写M的字符宽度。em是一个印刷排版的单位,表示字宽的单位。 em字面意思为:equal M(和M字符一致的宽度为一个单位)简称em。ems是em的复数表达。

引自http://blog.csdn.net/JavaLive09/article/details/38661773

maxEms与maxWidth很相似,只是单位不一样,都是限制TextView的最大宽度,所以可以和ellipsize属性共用。

Android自定义Notification布局的实现方法

android 调用popupwindow时activity变透明

image.png

popupwindow背景变暗后闪烁

背景变暗,一定要设置getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);否则背景会闪烁,特别针对的是根据变化动态裁剪图片。

  1. WindowManager.LayoutParams lp = getWindow().getAttributes();
  2. lp.alpha = 0.4f; //设置透明度
  3. getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
  4. getWindow().setAttributes(lp);

恢复

  1. mPopupWindow.setOnDismissListener(() -> {
  2. WindowManager.LayoutParams lp1 = getWindow().getAttributes();
  3. lp1.alpha = 1f;
  4. getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
  5. getWindow().setAttributes(lp1);
  6. });

image.png

获取屏幕信息及DP、SP、PX的转换

Android EditText 换行和对齐问题研究

Android textview换行属性有BreakStrategy和hyphenationFrequency。
对于TextView默认值是high_quality,对于EditText默认值是simple。

android 5.0以上通知栏、状态栏图标变成白色

瀑布流错乱和顶部空白问题解决

为什么会出现乱跳现象?
是因为recyclerview的复用机制遇上图片异步加载造成的,当瀑布流需要加载的图片的高度不一致时,假设第一个离开屏幕后的item的图片高度是100,被回收,而下一个要进入屏幕的item的图片高度是80,当图片还没加载完,下个item复用了第一个离开屏幕的item,产生了20的高度差,所以会出现跳动的现象,要避免跳动,需要从item的高度控制入手

Matisse 拍摄相片返回的Uri路径无法转换为真实路径的完美解决方案

特意的去翻了下源码,发现拍照生成的图片文件是固定的

  1. /**
  2. * 根据Uri获取文件真实地址
  3. */
  4. public static String getRealFilePath(Context context, Uri uri) {
  5. if (null == uri) return null;
  6. final String scheme = uri.getScheme();
  7. String realPath = null;
  8. if (scheme == null)
  9. realPath = uri.getPath();
  10. else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
  11. realPath = uri.getPath();
  12. } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
  13. Cursor cursor = context.getContentResolver().query(uri,
  14. new String[]{MediaStore.Images.ImageColumns.DATA},
  15. null, null, null);
  16. if (null != cursor) {
  17. if (cursor.moveToFirst()) {
  18. int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
  19. if (index > -1) {
  20. realPath = cursor.getString(index);
  21. }
  22. }
  23. cursor.close();
  24. }
  25. }
  26. if (TextUtils.isEmpty(realPath)) {
  27. if (uri != null) {
  28. String uriString = uri.toString();
  29. int index = uriString.lastIndexOf("/");
  30. String imageName = uriString.substring(index);
  31. File storageDir;
  32. storageDir = Environment.getExternalStoragePublicDirectory(
  33. Environment.DIRECTORY_PICTURES);
  34. File file = new File(storageDir, imageName);
  35. if (file.exists()) {
  36. realPath = file.getAbsolutePath();
  37. } else {
  38. storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
  39. File file1 = new File(storageDir, imageName);
  40. realPath = file1.getAbsolutePath();
  41. }
  42. }
  43. }
  44. return realPath;
  45. }

7.0手机拍摄后得到URI无法转化为路径

使用这个方法Matisse.obtainPathResult(data),获取路径

JAVA && 和 || 优先级的问题

加括号使用
false && false || true和 false && (false || true)结果不一样

EditText获取焦点时输入法闪一下然后消失

调用键盘和调用获取焦点的方法一起使用。
android:windowSoftInputMode=”adjustPan”

将简单的数据发送给其他应用

Android 为用户提供了两种在应用之间分享数据的方式:

  • Android Sharesheet 主要用于将内容发送到应用外部和/或直接发送给其他用户。例如,将网址分享给朋友。
  • Android intent 解析器最适合将数据传递到明确定义的任务的下一个阶段。例如,从应用中打开 PDF,并让用户挑选他们首选的查看器。