Android知识积累

Q:为什么整理知识,以及copy其它的文章?
A:因为之前收藏了好多很棒的文章,隔了好久后打开失效了,也是为了巩固一下知识,android知识比较杂乱,需要定期整理。

1.读取dp数据,返回px

getResources().getDimensionPixelOffset(R.dimen.xxx);

2.ListView与ScrollView滑动冲突解决

重写onMeasure

Android知识积累_第1张图片
image

3.沉浸式原理

  1. 隐藏状态栏,即全屏。

  2. 新建出一个状态栏代替。

decorView是一个FrameLayout,内部是一个LinearLayout(本身只有一个,内部是我们的布局,哈哈,废话!)。隐藏状态栏时(设置为透明),LinearLayout会上移,所以添加新的状态栏时(新建一个View占空间),需要进行margin/Padding处理(高度为getStatusBarHeight(activity)),或者设置LinearLayout的最外层布局属性android:fitsSystemWindows="true"。


Android知识积累_第2张图片
1
Android知识积累_第3张图片
2
Android知识积累_第4张图片
3

view.getRootView()获取到的是decorView,即根ViewGroup。decorView不包括状态栏和导航栏,它们属于SystemUI,当它们设置为透明或隐藏时,decorView高度会填充它们的位置。这也是沉浸式的原理。
getWindow().getDecorView()获取到的也是decorView!


额外说一下,android.R.id.content指的是上图中的ContentView(绿色框),切记!切记!别搞混了~~~~
findViewById(android.R.id.content)


4.Android5.0中的colorPrimary、colorPrimaryDark、colorAccent

Android知识积累_第5张图片
image

在5.0以上手机上直接设置colorPrimaryDark颜色,即可修改状态栏颜色;直接设置colorPrimary颜色,即可修改标题栏颜色;直接设置colorAccent颜色,即可修改默认的内容颜色。

5.padding和margin区别(与H5不一样)

Android知识积累_第6张图片
image

6.webView的padding设置有先天bug!

无论怎么设置padding都是不好使的,可以在web页面中设置padding,也可以在Android的webview加载页面时(原理一样),这样做,如下:


view.loadUrl(String.format(Locale.CHINA, "javascript:document.body.style.paddingTop='%fpx'; void 0", DensityUtil.px2dp(webView.getPaddingTop())));

7.RecyclerView中使用SnapHelper


SnapHelper snapHelper =new PagerSnapHelper();

snapHelper.attachToRecyclerView(recyclerView);

效果图:

image

8.performClick

performClick 是使用代码主动去调用控件的点击事件(模拟人手去触摸控件)


boolean android.view.View.performClick()

Call this view's OnClickListener, if it is defined.

Returns:

True there was an assigned OnClickListener that was called, false otherwise is returned.



@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_my);

btn1 = (Button) findViewById(R.id.button1);

tv1 = (TextView) findViewById(R.id.textView1);

btn1.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

tv1.setText("已经被点击过了");

}

});

// 模拟点击事件

btn1.performClick();

}

9.代码中设置Intent模式

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                Intent.FLAG_ACTIVITY_CLEAR_TOP |
                Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);

FLAG_ACTIVITY_NEW_TASK和Intent.FLAG_ACTIVITY_CLEAR_TOP一起使用的作用等同于singleTask。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS的意思是不在最近启动的列表中显示。

10.禁止截屏和录屏

设置Activity的属性:可防止系统截屏

this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);

11.设置application的allowBackup或者label冲突

  

冲突是由于该应用manifest中android:allowBackup和module或jar包中冲突导致,解决方法是使用tools:replace标注来替换,多个属性之间用“,”来分隔。

12.SurfaceView与TextureView的区别

SurfaceView有独立的Surface,通过“挖洞”原理显示它。以致它在执行旋转时,画面不会跟随旋转;同时设置透明度或者执行透明值动画时,显示有问题。Android N以上的SurfaceView在视频进行缩放旋转时会同步变化,不会看到黑色边,官方推荐使用SurfaceView。TextureView作为普通View在View hierarchy中管理与绘制,更适用于小窗播放视频功能。但TextureView需要硬件加速层,使得TextureView比SurfaceView和GLSurfaceView更耗性能。

原链接:https://cloud.tencent.com/developer/article/1034235

13.读取视频文件资源

AssetFileDescriptor afd1 = getResources().openRawResourceFd(R.raw.big_buck_bunny_720p_20mb);
try {
  mMediaPlayer.setDataSource(afd1.getFileDescriptor(), afd1.getStartOffset(), afd1.getLength());
} catch (IOException e) {
  e.printStackTrace();
}

14.TextView设置删除线/下划线/加粗

txt1 = ((TextView) findViewById(R.id.txt1));  
txt2 = ((TextView) findViewById(R.id.txt2));  
txt3 = ((TextView) findViewById(R.id.txt3));  
//添加删除线  
txt1.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG);  
//在代码中设置加粗  
txt2.getPaint().setFlags(Paint.FAKE_BOLD_TEXT_FLAG);  
//添加下划线  
txt3.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG);  

15.禁用截屏、录屏

在onCreate中加入如下代码:

this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, 
WindowManager.LayoutParams.FLAG_SECURE);

16.对于Application,Service,Activity三者的Context的应用场景

Android知识积累_第7张图片
context.png

其中: NO1表示 Application 和 Service 需要创建一个新的 task 任务队列,才可以启动一个 Activity。即intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
而对于 Dialog 而言,只有在 Activity 中才能创建。

17.内存泄漏总结

根本原因是:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收。

可以这么理解,长生命周期A含有短生命周期B的引用,此时需要回收B的内存,但是因为A(B),导致内存无法回收。

具体例子如下:

public class AppManager {
  private static AppManager instance;
  private Context context;
  private AppManager(Context context) {
    this.context = context;
  }
  public static AppManager getInstance(Context context) {
    if (instance == null) {
      instance = new AppManager(context);
    }
    return instance;
  }
}

1、如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。

2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

场景

  1. 类的静态变量持有大数据对象
    静态变量长期维持到大数据对象的引用,阻止垃圾回收。

  2. 非静态内部类的静态实例
    非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。

  3. 资源对象未关闭
    资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。

  4. 注册对象未反注册
    未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。

  5. Handler临时性内存泄露
    Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。
    由于AsyncTask内部也是Handler机制,同样存在内存泄漏的风险。
    此种内存泄露,一般是临时性的。

18.RxJava的些许问题

  • doOnNext

    public Observable getInfor() {
        Observable observable = retrofitService.getInfor()
  //              .observeOn(Schedulers.io())
                .doOnNext(new Consumer() {
                    @Override
                    public void accept(@NonNull TestBean testBean) throws Exception {
                        Log.d("LKK", "doOnNext=========" + Thread.currentThread().getName());
                    }
                })
                .compose(this.toMain());
        return observable;
    }

doOnNext是在onNext()之前运行的,并且默认在io线程运行。
注意:observeOn必须指定在doOnNext之前才起作用。

1.png

  • doOnSubscribe

我们知道observeOn()可以多次调用,实现线程的多次切换。但是subscribeOn()只是第一次的设置起作用。

Android知识积累_第8张图片
l.jpg

从图中可以看出,subscribeOn() 和 observeOn() 都做了线程切换的工作(图中的 "schedule..." 部位)。不同的是, subscribeOn() 的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。

这个时候要想subscribeOn起作用,需要用到Observable.doOnSubscribe() 。它和 Subscriber.onStart()同样是在subscribe()调用后而且在事件发送前执行,但区别在于它可以指定线程。默认情况下, doOnSubscribe() 执行在subscribe() 发生的线程;而如果在 doOnSubscribe() 之后有 subscribeOn() 的话,它将执行后面离它最近的 subscribeOn() 所指定的线程。

19.如何避免后台进程被杀死

  • 调用startForegound,让你的Service所在的线程成为前台进程
  • Service的onDestroy里面重新启动自己
  • Service的onStartCommond返回START_STICKYSTART_REDELIVER_INTENT
@Override  
   public int onStartCommand(Intent intent, int flags, int startId) {  
        ... ...
       return START_REDELIVER_INTENT;  
   }  

值得注意的是在onStartCommand中返回值,常用的返回值有:START_NOT_STICKYSTART_SICKYSTART_REDELIVER_INTENT,这三个都是静态常理值。

START_NOT_STICKY:表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,如果想重新实例化该Service,就必须重新调用startService来启动。

使用场景:表示当Service在执行工作中被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受的话,这是可以在onStartCommand返回值中设置该值。如在Service中定时从服务器中获取最新数据

START_STICKY:表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,这时onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。

使用场景:如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。

START_REDELIVER_INTENT:表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。

使用场景:如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。

20.App保活

参考:https://blog.csdn.net/AndrExpert/article/details/75045678

保活手段

当前业界的Android进程保活手段主要分为黑、白、灰三种,其大致的实现思路如下:

黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)
白色保活:启动前台Service
灰色保活:利用系统的漏洞启动前台Service

白色保活与灰色保活

①白色保活

  • 提升service优先级

AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

  • 服务设置为前台进程

白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。就像QQ音乐那样。

Android知识积累_第9张图片
image.png
public ForeService extends Service{
    public void onCreate(){
        super.onCreate();
        //创建NOtification对象
        Notification notificatin=new Notification(R.drawable.icon,"前台service",System.currentTImeMillis());
        Intent intent=new Intent(this,MainActivity.class);
        PendingIntent pending=PendingIntent.getActivity(this,0,intent,0);
        //为通知设置布局和数据
        notification.setLastestEventInfo(this,"","",pending);
        //将service设置为前台service
        startForeground(1,notification);
    }
}

② 灰色保活

这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。这样在低内存时被kill的几率会低一些。

方式按系统分为:

  • API<18 : 启动前台service,直接传入一个 new Notification()
  • API>18 : 同时启动两个NOTICE_ID相同的前台service,然后将后启动的service进行stop操作。(对API大于18而言,startForeground()方法需要弹出一个可见通知,如果你觉得不爽,可以开启另一个Service将通知栏移除)
/**前台Service,使用startForeground 
 * 这个Service尽量要轻,不要占用过多的系统资源,否则 
 * 系统在资源紧张时,照样会将其杀死 
 */  
public class DaemonService extends Service {  
    private static final String TAG = "DaemonService";  
    public static final int NOTICE_ID = 100;  
  
    @Nullable  
    @Override  
    public IBinder onBind(Intent intent) {  
        return null;  
    }  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        if(Contants.DEBUG)  
            Log.d(TAG,"DaemonService---->onCreate被调用,启动前台service");  
        //如果API大于18,需要弹出一个可见通知  
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){  
            Notification.Builder builder = new Notification.Builder(this);  
            builder.setSmallIcon(R.mipmap.ic_launcher);  
            builder.setContentTitle("KeepAppAlive");  
            builder.setContentText("DaemonService is runing...");  
            startForeground(NOTICE_ID,builder.build());  
            // 如果觉得常驻通知栏体验不好  
            // 可以通过启动CancelNoticeService,将通知移除,oom_adj值不变  
            Intent intent = new Intent(this,CancelNoticeService.class);  
            startService(intent);  
        }else{  
            startForeground(NOTICE_ID,new Notification());  
        }  
    }  
  
  
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        // 如果Service被终止  
        // 当资源允许情况下,重启service  
        return START_STICKY;  
    }  
  
  
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        // 如果Service被杀死,干掉通知  
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){  
            NotificationManager mManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);  
            mManager.cancel(NOTICE_ID);  
        }  
        if(Contants.DEBUG)  
            Log.d(TAG,"DaemonService---->onDestroy,前台service被杀死");  
        // 重启自己  
        Intent intent = new Intent(getApplicationContext(),DaemonService.class);  
        startService(intent);  
    }  
}  
/** 移除前台Service通知栏标志,这个Service选择性使用 
 * 
 * Created by jianddongguo on 2017/7/7. 
 * http://blog.csdn.net/andrexpert 
 */  
    
public class CancelNoticeService extends Service {  
    @Nullable  
    @Override  
    public IBinder onBind(Intent intent) {  
        return null;  
    }  
    
    @Override  
    public void onCreate() {  
        super.onCreate();  
    }  
    
    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2){  
            Notification.Builder builder = new Notification.Builder(this);  
            builder.setSmallIcon(R.mipmap.ic_launcher);  
            startForeground(DaemonService.NOTICE_ID,builder.build());  
            // 开启一条线程,去移除DaemonService弹出的通知  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    // 延迟1s  
                    SystemClock.sleep(1000);  
                    // 取消CancelNoticeService的前台  
                    stopForeground(true);  
                    // 移除DaemonService弹出的通知  
                    NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);  
                    manager.cancel(DaemonService.NOTICE_ID);  
                    // 任务完成,终止自己  
                    stopSelf();  
                }  
            }).start();  
        }  
        return super.onStartCommand(intent, flags, startId);  
    }  
    
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
    }  
}  
  • onDestroy方法里重启service
  • 1像素

锁屏时生成一个1像素的Activity在前台运行,保证不被系统杀死,解锁时销毁这个Activity

  • 双进程守护

顾名思义,启动两个Service互相监听,保证其中一个被杀死时,另一个将其拉起。

  • 播放音乐

Service在后台播放一个无声音乐,能够保证不被系统杀死。

当然,后两种方案需要设置onStartCommand的返回值为START_STICKYSTART_REDELIVER_INTENT

需要注意的是:
serviceonCreate只会执行一次,onBind也只会执行一次,onStartCommand可以执行多次。
也就是说多次创建service,不管是再次调用bindService还是startServiceonCreateonBind只会执行一次,多次调用startService会多次执行onstartCommand

21.LruCache 替代 SoftReference (软引用)

SoftReference 只有当系统内存不足时,GC清理时才会回收SoftReference标注的引用。它并不能及时的清理已经无用的引用对象,回收是不可控的。

LruCache(Least Recently Used),最近最少使用算法。
当成一个 Map 用就可以了,只不过实现了 LRU 缓存策略。

private static final float ONE_MIB = 1024 * 1024;
// 7MB
private static final int CACHE_SIZE = (int) (7 * ONE_MIB);
private LruCache bitmapCache;
this.bitmapCache = new LruCache(CACHE_SIZE) {
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount();
    }

    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
        ...
    }
};

注意
1.需要提供一个缓存容量作为构造参数。
2.覆写 sizeOf 方法 ,自定义设计一条数据放进来的容量计算,如果不覆写就无法预知数据的容量,不能保证缓存容量限定在最大容量以内。

因为getByteCount要求的API版本较高,因此对于使用较低版本的开发者,getRowBytes() * getHeight()

LruCache原理

内部由一个LinkedHashMap存储缓存数据,而LinkedHashMap的特性是最新访问的数据排在后面

例如:

public static final void main(String[] args) {
        LinkedHashMap map = new LinkedHashMap<>(0, 0.75f, true);
        map.put(0, 0);
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        map.put(4, 4);
        map.put(5, 5);
        map.put(6, 6);
        map.get(1);
        map.get(2);

        for (Map.Entry entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());

        }
    }

输出结果:

0 0
3 3
4 4
5 5
6 6
1 1
2 2
Android知识积累_第10张图片
image.png

即:当LruCache空间满了的时候,去除LinkedHashMap的第一组数据(最旧的,使用最少的),并将新的数据填加在最后(最新的)。
map中的第一个元素就是最近最少使用的那个元素。

原文章:https://www.jianshu.com/p/31ca573a59e0

22.ViewPager左右禁止滑动

/**
 * 禁止左右滑动
 * Created by LiuKuo at 2018/5/22
 */

public class CustomeViewPager extends ViewPager {
    /**
     * true:可以滑动;false:不能滑动
     */
    private boolean isScoll;

    public CustomeViewPager(Context context) {
        this(context, null);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return isScoll && super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return isScoll && super.onTouchEvent(ev);
    }

    public void setScoll(boolean scoll) {
        isScoll = scoll;
    }
}

23.OkHttp写法

法1:

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
       .readTimeout(DEFAULT_READ_TIME_OUT, TimeUnit.SECONDS)
       .writeTimeout(DEFAULT_WRITE_TIME_OUT, TimeUnit.SECONDS)
       .addInterceptor(interceptor);
OkHttpClient client = bulider.build();

法2:

OkHttpClient client = new OkHttpClient().newBuilder()
       .connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
       .readTimeout(DEFAULT_READ_TIME_OUT, TimeUnit.SECONDS)
       .writeTimeout(DEFAULT_WRITE_TIME_OUT, TimeUnit.SECONDS)
       .addInterceptor(interceptor)
       .build();

24.更换主题

@Override
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        final Context contextThemeWrapper = new ContextThemeWrapper(
                getActivity(), isDay ? R.style.day : R.style.night);
        LayoutInflater localInflater = inflater
                .cloneInContext(contextThemeWrapper);
        View v = localInflater.inflate(R.layout.fragment_layout, container,
                false);
        return v;
    }

25.app包和v4包中的fragment的区别

1、app包中的fragment是在3.0之后才有的,支持的版本太高。
2、v4包中的Fragment可以兼容到1.6的版本。

所以,日常使用时,一般使用V4包中的fragment

补充:
学习Glide源码时,其中发现.with()的源码中,

  • Activityandroid.app.Fragment归为一类处理,他们都是3.0之后的。
  • FragmentActivityandroid.support.v4.app.Fragment归一类处理,他们可以支持到1.6版本。

Fragment是Android 3.0以后的东西,为了在低版本中使用Fragment就要用到android-support-v4.jar兼容包,而FragmentActivity就是这个兼容包里面的,它提供了操作Fragment的一些方法,其功能跟3.0及以后的版本的Activity的功能一样。

26.Android 5.0新增跳转动画

1.gif.gif
Pair p1 = Pair.create((View)holder.imgBg, "img_view_1");
Pair p2 = Pair.create((View)holder.tvTitle, "title_1");
Pair p3 = Pair.create((View)holder.tvBottom, "tv_bottom");
ActivityOptionsCompat options = ActivityOptionsCompat.
         makeSceneTransitionAnimation(Activity_2.this, p1,p2,p3);
startActivity(intent,options.toBundle());

xml布局

    
    

27.RecyclerView的getViewForPosition和getChildAt区别

  • getViewForPosition(RecyclerView的方法)
    获取的是RecyclerView顺序的子元素。
  • getChildAt (ViewGroup的方法)
    获取的是子View叠加出现的顺序。

例如,一个叠加显示的RecyclerView(最上层显示第1个子View),它的getViewForPosition(0)返回的是最上层的View,而getChildAt(0)返回的是最下层的子View,它只与显示的层级有关,和RecyclerView的顺序无关。

28.伸缩效果的EditText

Android知识积累_第11张图片
    

        
    

29.startActivityForResult的使用

说起来丢人,真丢人,这个我竟然搞混了!!!不记下来不行

Activity1.class

Intent intent = new Intent(Activity1.this, Activity2.class);
startActivityForResult(intent, 1);

//返回接收处理
   @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        
    }

Activity2.class

Intent i = new Intent();
setResult(1, i);
finish();

说一下搞混的内容:

  • 1.Activity2中的Intent,不需要设置具体的Class。
  • 2.setResult语句不会直接返回!不会返回!不会返回,我竟然记成了它会直接返回 :( ,需要调用finish()手动关闭当前页面,然后会回调onActivityResult

30.PorterDuff.Mode

官方给出的是两个w,h相同的Bitmap叠加的效果,而不是一个Bitamp中,两个图形的叠加效果,正确的为:


Android知识积累_第12张图片
image.png

黄色的圆先画,蓝色的方块后画。

https://blog.csdn.net/u013085697/article/details/52096703

31.Fragment通信

  • Fragment与Activity间通信

①Activity向Fragment发送信息(通过setArguments(Bundle)

Activity.class

FragmentTransaction transaction = mManager.beginTransaction();
RightFragment fragment = new RightFragment();
//创建Bundle对象
Bundle bundle = new Bundle();
//向Bundle输入数据
bundle.putInt("data", 100);
//对fragment设置Bundle参数
fragment.setArguments(bundle);
transaction.replace(R.id.fl_content, fragment);
transaction.commit();

Fragment.class

Bundle bundle = getArguments();

当然也可以通过接口的方式实现。

②Fragment向Activity发送信息

Fragment.class

//强制转换为当前Activity对象后,直接调用它的方法
((MainActivity)getActivity()).test(String sss);

Activity.class

public void test(String s){
    Toast.makeText(this,s,Toast.LENGTH_LONG).show();
}

这种方式简单粗暴,你也可以自己定义接口,如下:
Interface.class

public interface Listener {  
    void setText(String s);  
}  

Activity.class

public class MainActivity extends AppCompatActivity implements Listener {
    ... ...
    @Override
    public void setText(String s){
        Toast.makeText(this,s,Toast.LENGTH_LONG).show();
    }
}

Fragment.class

((Listener)getActivity()).setText(String sss);
  • Fragment与Fragment间通信

Fragment之间是不能直接通信的,需要Activity来协助。
Fragment1 -> Activity ->Fragment2

比如一个Activity有两个Fragment,点击左侧Fragment1的按钮,右侧Fragment2会变色。
Fragment1.class

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ((MainActivity)getActivity()).setColor(red);
    }
}

MainActivity.class

public void setColor(int color){
    FragmentManager fm = getSupportFragmentManager();
    fragment2 = fm.findFragmentByTag("fragment2");
    fragment2.setColorChange(color);
}

Fragment2.class

private void setColorChange(int color){
    layout.setBackgroundColor(color);
}

32.GreenDao的使用

在工程的build.gradle中添加

dependencies {
     ... ...
     classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
}

在项目的build.gradle中添加

apply plugin: 'org.greenrobot.greendao'

android {
  ... ...
  greendao {
     //版本号,升级时可配置
     schemaVersion 1
     //dao的包名,包名默认是entity所在的包
     daoPackage 'com.example.common.dao'
     //生成数据库文件的目录
     targetGenDir 'src/main/java'
  }
}

dependencies {
  ... ...
  api 'org.greenrobot:greendao:3.2.2'
}

BaseApplication.class

public class BaseApplication extends Application {
    private static DaoSession mDaoSession;

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

    private void initGreenDao() {
        DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "lk.db", null);
        SQLiteDatabase sqLiteDatabase = devOpenHelper.getWritableDatabase();
        DaoMaster daoMaster = new DaoMaster(sqLiteDatabase);
        mDaoSession = daoMaster.newSession();
    }

    public static DaoSession getDaoSession() {
        return mDaoSession;
    }
}

注意:如果不先创建相关Bean类的情况下,是找不到DaoMaster相关的!!!

工具类

public class GdUserUtil {

    private static GdUserDao getDao() {
        DaoSession session = BaseApplication.getDaoSession();
        GdUserDao dao = session.getGdUserDao();
        return dao;
    }

    public static List queryAllData() {
        return getDao().loadAll();
    }

    public static List queryData(String name) {
        return getDao().queryBuilder().where(GdUserDao.Properties.Name.eq(name)).list();
    }

    public static void insertData(GdUser user) {
        getDao().insert(user);
    }

    public static void removeData(GdUser user) {
        getDao().delete(user);
    }

    public static void removeData(Long key) {
        getDao().deleteByKey(key);
    }

    public static void updateData(GdUser user) {
        getDao().update(user);
    }
}

报错:android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: GD_USER._id
GdUser.class

@Entity
public class GdUser {
    @Id(autoincrement = true)
    private long id;
    private String name;
    private String age;
}

错误原因就是。。。别打我:) 。。。 id的类型必须设置为Long,大写的L,是包装类,不是基本数据类型,一定要注意。

报错
Re-download dependencies and sync project (requires network)
Stop Gradle build processes (requires restart)

官方给出的答案是:支持的最小gradle是2.3.2。
Please update your Android Gradle Plugin to 2.3.2 (com.android.tools.build:gradle:2.3.2). Version 2.2.x is not supported.

33. Handler内存溢出问题

Android知识积累_第13张图片

在Handler消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类MainActivity,但由于上述引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。

解决方法:

private static class MyHandler extends Handler{
        // 定义 弱引用实例
        private WeakReference reference;
        // 在构造方法中传入需持有的Activity实例
        public FHandler(Activity activity) {
            // 使用WeakReference弱引用持有Activity实例
            reference = new WeakReference(activity); 
        }
        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            Activity activity = reference.get();
            if(activity != null){
                ... ...
            }
        }
 }

注意handler.postDelayed()无法防止内存溢出,最好使用sendMessage这种形式。

PS:当可能出现Handler内存溢出时,AS会提示。。。

34.new Handler()和new Handler(Looper.getMainLooper())区别

如果你不带参数的实例化:Handler handler = new Handler();会默认用当前线程的Looper对象。

一般而言,如果你的Handler是要用来刷新UI的,那么就需要在主线程下运行。

情况:

  • 刷新UI,handler要用到主线程的Looper对象。那么在主线程Handler handler=new Handler();如果在其他非主线程也要满足这个功能的话,要Handler handler=new Handler(Looper.getMainLooper());

  • 不用刷新UI,只是处理消息。当前消息如果是主线程的话,Handler handler=new Handler();;非主线程的话,Looper.prepare(); Handler handler=new Handler();Looper.loop()或者Handler handler=new Handle(Looper.getMainLooper());

若是实例化的时候调用Looper.getMainLooper()就表示放到主线程中去处理;若有不是的话,UI 线程默认执行过Loop.prepare()Loop.loop(),其他线程需要手动调用这两个。否则会报错。

35.ImageView.ScaleType设置图解

  1. android:scaleType=“center”
    保持原图的大小,显示在ImageView的中心。当原图的size大于ImageView的size时,多出来的部分被截掉。
  2. android:scaleType=“center_inside”
    以原图正常显示为目的,如果原图大小大于ImageView的size,就按照比例缩小原图的宽高,居中显示在ImageView中。如果原图size小于ImageView的size,则不做处理居中显示图片。
  3. android:scaleType=“center_crop”
    以原图填满ImageView为目的,如果原图size大于ImageView的size,则与center_inside一样,按比例缩小,直到宽或高等于ImageView的size,其余部分裁剪,居中填充满显示在ImageView上。如果原图size小于ImageView的size,则按比例拉升原图的宽和高,直到宽或高等于ImageView的size,其余部分裁剪,填充满ImageView居中显示。
  4. android:scaleType=“matrix”
    不改变原图的大小,从ImageView的左上角开始绘制,超出部分做剪切处理。
  5. androd:scaleType=“fit_xy”
    把图片按照指定的大小在ImageView中显示,拉伸显示图片,不保持原比例,填满ImageView.
  6. android:scaleType=“fit_start”
    把原图按照比例放大缩小到ImageView的高度,显示在ImageView的start(前部/上部)。
  7. android:sacleType=“fit_center”
    把原图按照比例放大缩小到ImageView的高度,显示在ImageView的center(中部/居中显示)。
  8. android:scaleType=“fit_end”
    把原图按照比例放大缩小到ImageView的高度,显示在ImageVIew的end(后部/尾部/底部)
Android知识积累_第14张图片

https://blog.csdn.net/larryl2003/article/details/6919513
https://blog.csdn.net/u012702547/article/details/50586946

36.getRawX和getX区别

Android知识积累_第15张图片

37.WindowManager添加控件显示悬浮框时遇到的问题

  • 1.默认控件显示的位置是正中间,这是因为中间的坐标为(0,0)。
  • 2.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;不设置的话,悬浮框始终获取焦点,此时无法操作,桌面都回不到了。
  • 3.作touch设置时,要使用down和move的getRawX/Y的差值+lp.x/y,不能直接使用getRawX/Y,因为此时屏幕的左上角不是原点。

38.View的位置坐标

View 提供了如下 5 种方法获取 View 的坐标:

  • View.getTop()、View.getLeft()、View.getBottom()、View.getRight();
  • View.getX()、View.getY();
  • View.getTranslationX()、View.getTranslationY();
  • View.getLocationOnScreen(int[] position);
  • View.getLocationInWindow(int[] position);
1. View.getTop()、View.getLeft()、View.getBottom()、View.getRight()
Android知识积累_第16张图片

需要注意的是,这四个方法获取的坐标表示的是View原始状态时相对于父容器的坐标,对View的平移操作不会改变这四个方法的返回值!!!

2. View.getX()、View.getY()

getX()与getY()方法获取的是View左上角相对于父容器的坐标,当View没有发生平移操作时,getX()==getLeft()getY==getTop()

3. View.getTranslationX()、View.getTranslationY()

translationXtranslationY是View左上角相对于父容器的偏移量:translationX = getX() - getLeft(),当View未发生平移操作时,translationXtranslationY都为0。

4. getLocationOnScreen

获取View相对于整个屏幕的坐标。

5. getLocationInWindow

获取View相对于Window的坐标(忽略状态栏及ActionBar)。

Android知识积累_第17张图片

其中, getLocationInWindow是以B为原点的C的坐标
其中, getLocationOnScreen是以A为原点的C的坐标

Android知识积累_第18张图片
getLocationOnScreen、getLocationInWindow区别

39.Activity的构造方法

正如《Android Activity的启动和跳转》一文所说,在Android APP中启动一个Activity都是通过startActivity()或startActivityForResult()来实现,并不会直接在APP代码中new一个Activity对象来使用,直接new出来的Activity对象是无法使用的。一般来说我们在实现一个Activity类时会将初始化的一些操作放到onCreate()中执行,并不会实现其构造方法。但是这并不是说Activity类没有构造方法,或者构造方法不会被调用。

事实上,调用startActivity()或startActivityForResult()之后,Android框架中的代码会完成Activity对象的创建。在创建Activity对象时仍然会执行Activity类的构造方法。

在android.app.Instrumentation类中有一个newActivity方法。Android框架正是通过此方法来创建一个Activity对象的。其代码如下。

public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    return (Activity)cl.loadClass(className).newInstance();
}

可以看到,这里通过ClassLoader的loadClass()方法来加载指定的Activity类,得到对应的Class对象,然后再调用Class对象的newInstance()方法创建Activity对象。

由于Class对象的newInstance()方法在创建对象时会调用该类的无参构造方法,因此,如果确实需要在Activity构造的时候执行一些操作,可以在这个Activity类中增加一个无参的构造方法,这个构造方法会在newInstance()时被自动调用。

这里有两点需要注意:

  1. 一个Activity类中只有无参的构造方法会被执行,定义有参数的构造方法是没有意义的。
  2. 一个Activity类中如果没有无参构造方法,或者无参构造方法不是public的,则在执行newInstance()的时候会产生异常。

40.scrollTo 和scrollBy 相关问题

  • 每一个View中,系统都提供了scrollToscrollBy两种方式来改变一个View的位置。这两个方法的区别非常好理解,与英文中To与By的区别类似,scrollTo(x, y)标识移动到一个具体的坐标点(x, y),而scrollBy(dx, dy)表示移动的增量为dx、dy。

但是,当我们拖动View的时候,你会发现View并没有移动!难道是我们方法写错了吗?其实,方法并没有写错,View也确实移动了,只是它移动的并不是我们想要移动的东西。scrollToscrollBy方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollToscrollBy方法,那么移动的将是所有子View,但如果在View中使用,那么移动的将是View的内容,例如TextView,content就是它的文本;ImageView,content就是它的drawable对象。
相信通过上面的分析,应该知道为什么不能再View中使用这两个方法来拖动这个View了。那么我们就该View所有的ViewGroup中来使用scrollBy方法,移动它的子View,代码如下所示:
((View) getParent()).scrollBy(-offsetX,-offsetY);

注意:滚动的正负号!!!!(向上滚动-offsetY,向左滚动-offsetX)

41.底部弹窗

1.PopupWindow实现底部弹窗
2.dialog实现底部弹窗
3.dialogFragment实现底部弹窗
4.BottomSheetDialog实现底部弹窗

https://www.jianshu.com/p/8292fdbc4c2b

42.Activity动画

  1. 使用 style 的方式定义 Activity 的切换动画
  2. 使用 overridePendingTransition 方法实现 Activity 跳转动画
  3. 使用 ActivityOptions 切换动画实现 Activity 跳转动画(部分动画可支持到 api >= 16)
  4. 使用 ActivityOptions 动画共享组件的方式实现跳转 Activity 动画(api >= 21)

overridePendingTransition方法必须在startActivity的后面或者finish()之后被调用才生效。

43.PriorityQueue优先级队列

        PriorityQueue bundleQueue = new PriorityQueue<>(
                10,
                new Comparator() {
                    @Override
                    public int compare(BundleProxy o1, BundleProxy o2) {
                        return o2.getConfig().p.value() - o1.getConfig().p.value();
                    }
                }
        );

44.RecyclerView获取滚动距离

仅限于LinearLayout

public int getScollYDistance() {
       LinearLayoutManager layoutManager = (LinearLayoutManager) this.getLayoutManager();
       int position = layoutManager.findFirstVisibleItemPosition();
       View firstVisiableChildView = layoutManager.findViewByPosition(position);
       int itemHeight = firstVisiableChildView.getHeight();
       return (position) * itemHeight - firstVisiableChildView.getTop();
   }

45.mipmap

Android知识积累_第19张图片
mipmap

46.

你可能感兴趣的:(Android知识积累)