文章目录
【68.让ProgressDialog在setCancelable(false)时按返回键可dismiss】
主要是为 ProgressDialog 添加 KeyListener 来对返回键予以处理
private ProgressDialog progressDialog = null;
/**
* show loading progress dialog
*/
public void showDialog() {
if (null == progressDialog) {
progressDialog = ProgressDialog.show(BaseActivity.this, "", "正在加载,请稍候...");
progressDialog.setCancelable(false);
} else {
progressDialog.show();
}
progressDialog.setOnKeyListener(onKeyListener);
}
/**
* add a keylistener for progress dialog
*/
private OnKeyListener onKeyListener = new OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
dismissDialog();
}
return false;
}
};
/**
* dismiss dialog
*/
public void dismissDialog() {
if (isFinishing()) {
return;
}
if (null != progressDialog && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
/**
* cancel progress dialog if nesseary
*/
@Override
public void onBackPressed() {
if (progressDialog != null && progressDialog.isShowing()) {
dismissDialog();
} else {
super.onBackPressed();
}
}
【dialog监听back】
dialog.setCanceledOnTouchOutside(false); //外部不行,back可以
dialog.setOnCancelListener(dialog->{
//监听back按钮的
})
【69.传递数据 Intent,LiveDataBus,EventBus】
-
1.bundle内部实现是ArrayMap,在小数据存储的时候,效率比Hashmap高,而一般需要使用bundle的场景数据都比较小。
-
2.bundle使用ParceLable序列化对象,而Hashmap是java的类,使用的是Serializable,效率上bundle高。
https://blog.csdn.net/H291850336/article/details/50515705
在跨多个Activity进行传递数据时最好不要用Intent,容易造成内存泄漏。 -
3.String类型效率比Serializable,Parcelable低。
private MyBean(Parcel in) {
mField = in.readInt();//普通整型
mMyEnum = MyEnum.values()[in.readInt()];//枚举类型,此枚举类不需要实现Parcelable
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mField);//普通整型
dest.writeInt(mMyEnum.ordinal());//枚举类型
}
通信方案 | 优点 | 缺点 |
---|---|---|
handler | 系统原生,能实现线程间通信 | 高耦合 不利于维护 容易导致内存泄漏和空指针 |
broadcast | 简单 | 性能差 传播数据有限 打乱代码的执行逻辑 |
interface | 速度快,容易理解 | 实现复杂,不利于维护 |
rxBus | 效率高,无内存泄漏 | 基于rxjava,学习成本高且依赖包太大,rxjava2.2M |
EventBus | 使用简单 | 混淆问题 无法感知组件生命周期 实现复杂 |
LiveDataBus | 实现极其简单,代码量少 官方提供稳定的依赖代码 感知组件生命周期 不会造成内存泄漏 |
LiveDataBus:核心部分
Lifecycle
Lifecycle是Android官方推出的架构之一,它具有生命周期感知功能,不但能够监听Activity和Fragment的生命周期,还能回调相应方法,同时能够实时的获取当前Activity和Fragment的状态。
LiveData
LiveData是一个数据持有类,持有数据并且这个数据能够被观察者所监听到,而且他是和Lifecycle绑定的,具有生命周期感知,解决内存泄露和引用问题。
什么是Hook技术?
Hook即“钩子”,他可以在事件传送中截获并监控事件传输,将自身的代码与系统方法进行融入。这样当方法被调用时,也就可以执行我们自己的代码,这也是面向切面编程的思想(AOP)。
其实就是通过反射获取到“Hook”点,然后在Hook点执行我们要插入的方法或者改变其原本逻辑的参数,然后在接着执行它原本要执行的逻辑。
【70.资源警告No package identifier】
去除 No package identifier when getting name for resource number 0x00000000 错误信息
将所有颜色信息移动到color.xml内避免此错误
Glide位图警告 (未解决):
https://github.com/bumptech/glide/issues/743
W/Bitmap: Called reconfigure on a bitmap that is in use! This may cause graphical corruption!
旧版 | glide 4.9.0 |
---|---|
crossFade(500) | transition(DrawableTransitionOptions.withCrossFade(500)) |
transform(new GlideCircleTransform(v.getContext())) | transform(new GlideCircleTransform()) |
bitmapTransform(new BlurTransformation(this, 23, 4)) | apply(bitmapTransform(new BlurTransformation( 50, 8))) |
listener(new RequestListener<String, GlideDrawable>()… | listener(new RequestListener()… |
- glide 4.9.0 可以直接配置圆形和圆角图片 transforms(new CircleCrop())
- 渐变设置和监听设置有更改
- asBitmap() 需要设置在 load(url)之前
// 官方 Glide
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// 高斯模糊和圆角等
implementation 'jp.wasabeef:glide-transformations:4.0.1'
GLide参考:https://cloud.tencent.com/developer/article/1424574
https://www.jianshu.com/p/fd464ce87c79
https://muyangmin.github.io/glide-docs-cn/doc/getting-started.html
替换Glide通讯组件为Okhttp并监控加载进度
https://blog.csdn.net/huangxiaoguo1/article/details/78595627
【72.Handler警告】
- 非静态Handler导致Activity泄漏
初始:
public class ActivityA extends AppCompatActivity{
//泄露,一般置为 static
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
}
}
@Override
public void onCreate(Bundle savedInstanceState){
handler.postDelayed(new Runnable(){
@Override
public void run(){
try{ Thread.sleep(10000); }
catch(Exception e){...}
}
},3000);
}
}
修改:
private MyHandler handler;
protected void setUpData() {
handler = new MyHandler(this);
handler.sendEmptyMessageDelayed(0, 1000);
}
@Override
protected void onPause() {
super.onPause();
handler.removeMessages(0);
}
static class MyHandler extends Handler {
private final WeakReference<Activity> mActivity;
public MyHandler(Activity activity) {
this.mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Activity activity = mActivity.get();
if (activity != null) {
}
}
}
【73.List集合 containsAll 方法】
1. java中的list是有contains方法:
判断列表中是否包含指定元素。如果列表中包含指定元素,则返回true,否则返回false。
list.add("菠萝"); //向列表中添加数据
String o = "苹果";
list.contains(o); //返回false
2. java中的list是有containsAll方法:
判断B链表是不是A链表的子集,我们可以使用A.containsAll(B)来判断,
当返回值是true的时候就表明B链表是A链表的子集,
当返回值是false时候就表明B链表不是A链表的子集。
ArrayList<String> als = new ArrayList<String>();
als.add("a");
als.add("b");
ArrayList<String> alss = new ArrayList<String>();
alss.add("a");
alss.add("c");
System.out.println(als.containsAll(alss));
实验结果:false
源代码:
public boolean containsAll(Collection<?> c) {
Iterator<?> e = c.iterator();
while (e.hasNext())
if (!contains(e.next()))
return false;
return true;
}
【74.判断是否为 null】
//其中null代表某个变量
if("0".equals(null)){
//可以判断
}
if(null.equals("0")){
//会崩溃
}
null.equals("abc") → 抛出 NullPointerException 异常
"abc".equals(null) → 返回 false
null.equals(null) → 抛出 NullPointerException 异常
=》
Objects.equals(null, "abc") → 返回 false
Objects.equals("abc",null) → 返回 false
Objects.equals(null, null) → 返回 true
"abc".equals("") → 返回 false
"".equals("abc") → 返回 false
"".equals("") → 返回 true
=》
Objects.equals("abc", "") → 返回 false
Objects.equals("","abc") → 返回 false
Objects.equals("","") → 返回 true
Integer
null==1,还是 1==null都崩溃
【75.设置字体】
Android系统默认字体支持四种字体,分别为:
- noraml (普通字体,系统默认使用的字体)
- sans(非衬线字体)
- serif (衬线字体)
- monospace(等宽字体)
关于后三种字体的区别可以看:
http://kb.cnblogs.com/page/192018/
- 在xml中修改字体
<!-- 使用默认的sans字体-->
<TextView
android:id="@+id/sans"
android:text="Hello,World"
android:textSize="20sp"
android:typeface="sans" />
<!-- 使用默认的serifs字体-->
<TextView
android:id="@+id/serif"
android:text="Hello,World"
android:textSize="20sp"
android:typeface="serif" />
<!-- 使用默认的monospace字体-->
<TextView
android:id="@+id/monospace"
android:text="Hello,World"
android:textSize="20sp"
android:typeface="monospace" />
- 在java代码中修改字体
//设置serif字体
textView.setTypeface(Typeface.SERIF);
//设置sans字体
textView.setTypeface(Typeface.SANS_SERIF);
//设置monospace字体
textView.setTypeface(Typeface.MONOSPACE);
- 在Android中可以引入其他字体
在assets目录下新建fonts目录,把ttf字体文件放到这
//得到AssetManager
AssetManager mgr=getAssets();
//根据路径得到Typeface
Typeface tf=Typeface.createFromAsset(mgr, "fonts/pocknum.ttf");
//在实际使用中,字体库可能存在于SD卡上,可以采用createFromFile()来替代createFromAsset。
//String path =Environment.getExternalStorageDirectory().getAbsoluteFile() + File.separator + "xxx.ttf";
//Typeface typeface2 =Typeface.createFromFile(path);
//设置字体
textView.setTypeface(tf);
【76.布局优化】
Android布局优化:include 、merge、ViewStub的详细总结
merge标签必须使用在根布局,并且ViewStub标签中的layout布局不能使用merge标签.
事件拦截:
https://blog.csdn.net/weixin_37228152/article/details/103865151
【77.判断当前APP后台运行】
切换回界面输入密码等
//判断当前APP后台运行
private boolean isAppBg(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//这是把所有进程全部取出了,【不行】
List<ActivityManager.RunningAppProcessInfo> appProcessList = am.getRunningAppProcesses();
if (appProcessList == null) {
return false;
}
for (ActivityManager.RunningAppProcessInfo appProcess :
appProcessList) {
if (appProcess.processName.equals(context.getPackageName())) {
if (appProcess.importance != ActivityManager.RunningAppProcessInfo
.IMPORTANCE_FOREGROUND) {
Log.e(context.getPackageName(), "处于后台" + appProcess.processName);
return true;
} else {
Log.e(context.getPackageName(), "处于前台" + appProcess.processName);
return false;
}
}
}
return false;
}
利用这个手机 多任务单进程,只会有一个在前台显示
public class BaseApplication extends Application {
private int appCount = 0;
@Override
public void onCreate() {
super.onCreate();
this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
appCount++;
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
appCount--;
if (appCount == 0) {
Toast.makeText(getApplicationContext(),"切入后台",Toast.LENGTH_SHORT).show();
}
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
});
}
}
【78.Android第一次安装后Home键重启问题】未验证
Android应用第一次安装成功点击“打开”后Home键切出应用后再点击桌面图标返回导致应用重启问题
//isTaskRoot是Activity系统方法
if (!this.isTaskRoot()) {
Intent mainIntent = getIntent();
String action = mainIntent.getAction();
if (mainIntent.hasCategory(Intent.CATEGORY_LAUNCHER) && action.equals(Intent.ACTION_MAIN)) {
finish();
return;
}
}
@Override
public void onCreate(Bundle savedInstanceState){
//Android第一次安装打开,home键再点击启动,程序重复启动
if (!isTaskRoot()){
finish();
return;
}
}
【番外:引入第三方包报错】
https://blog.csdn.net/chenlove1/article/details/60958886
【79.一些属性-横竖屏】
a)Aandroid设置横屏和竖屏的方法:
AndroidManifest.xml中:android:screenOrientation
"unspecified":默认值 由系统来推断显示方向.判定的策略是和设备相关的,所以不同的设备会有不同的显示方向.
"landscape":横屏显示(宽比高要长)
"portrait":竖屏显示(高比宽要长)
"user":用户当前首选的方向
"behind":和该Activity以下的那个Activity的方向一致(在Activity堆栈中的)
"sensor":有物理的感应器来决定。假设用户旋转设备这屏幕会横竖屏切换。
"nosensor":忽略物理感应器。这样就不会随着用户旋转设备而更改了("unspecified"设置除外)。
方法二:在java代码中设置
设置横屏代码:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//横屏
设置竖屏代码:setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//竖屏
if(this.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
b)android:configChanges属性
一些设备的配置可能会改变,如:横竖屏的切换、键盘的可用性等。这些事件一旦发生,当前活动的Activity会重新启动,其中的过程是:在销毁之前会先调用onSaveInstanceState()方法去保存你应用中的一些数据,然后调用onDestroy()方法,最后调用onCreate()、onStart()、onResume()等方法启动一个新的Activity。
“screenLayout”: 屏幕的显示发生了变化------不同的显示被激活
“screenSize”: 屏幕大小改变了
https://blog.csdn.net/hanyingjie327/article/details/21246545
AlarmManager定时闹钟的用法:
https://www.cnblogs.com/ProtectedDream/p/6351447.html
【80.if 与 switch】
-
1.当分支较多时,使用switch的效率是很高的。因为switch是随机访问的,就是确定了选择值之后直接跳转到那个特定的分支,但是if。。else是遍历所有的可能值,直到找到符合条件的分支。如此看来,switch的效率确实比ifelse要高的多。
-
2.由汇编代码可知道,switch…case占用较多的代码空间,因为它要生成跳表,特别是当case常量分布范围很大但实际有效值又比较少的情况,switch…case的空间利用率将变得很低。
-
3.switch…case只能处理case为常量的情况,对非常量的情况是无能为力的。例如 if (a > 1 && a < 100),是无法使用switch…case来处理的。所以,switch只能是在常量选择分支时比ifelse效率高,但是ifelse能应用于更多的场合,ifelse比较灵活。
一般5个选项(包括default)的情况下,switch和if/else if相同。低于5个选项if快,高于5给选项switch快。
在AndroidLibrary中view.getId不能用switch分支:
原因是:Resource IDs cannot be used in a switch statement in Android library modules
在Android library中不能使用switch-case语句访问资源ID,问题的原因是Android library中生成的R.java中的资源ID不是常数
在library中通过if-else-if条件语句来引用资源ID,这样就避免了这个错误。public void onClick(View src){ int id = src.getId(); if (id == R.id.playbtn){ // ... } else if (id == R.id.stopbtn){ // ... } else if (id == R.id.btnmenu){ openOptionsMenu(); } }
【81.通知栏权限关闭 吐司不显示】
小米显示,华为,三星,魅族,乐视等不显示
Toast通知栏权限填坑指南
Android部分手机通知权限关闭无法打出Toast
Toast怎么支持点击事件?https://github.com/getActivity/XToast 使用
// 判断是否为 Android 6.0 及以上系统并且有悬浮窗权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(mToast.getView().getContext())) {
// 解决使用 WindowManager 创建的 Toast 只能显示在当前 Activity 的问题
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
}
/**
* 检查通知栏权限有没有开启
*/
public static boolean isNotificationEnabled(Context context){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).areNotificationsEnabled();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
ApplicationInfo appInfo = context.getApplicationInfo();
String pkg = context.getApplicationContext().getPackageName();
int uid = appInfo.uid;
try {
Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName());
Method checkOpNoThrowMethod = appOpsClass.getMethod("checkOpNoThrow", Integer.TYPE, Integer.TYPE, String.class);
Field opPostNotificationValue = appOpsClass.getDeclaredField("OP_POST_NOTIFICATION");
int value = (Integer) opPostNotificationValue.get(Integer.class);
return (Integer) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg) == 0;
} catch (NoSuchMethodException | NoSuchFieldException | InvocationTargetException | IllegalAccessException | RuntimeException | ClassNotFoundException ignored) {
return true;
}
} else {
return true;
}
}