Android小提示五

【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低。

枚举Enum序列化

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/

  1. 在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" />
    
  1. 在java代码中修改字体
//设置serif字体
textView.setTypeface(Typeface.SERIF);
//设置sans字体
textView.setTypeface(Typeface.SANS_SERIF);
//设置monospace字体
textView.setTypeface(Typeface.MONOSPACE);
  1. 在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;
    }
}

注释的一些小技巧:https://www.cnblogs.com/wangyun/p/9176356.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值