常用优化项

优化

  • Glide
  • onDestroy 延迟执行
  • 资源图片
  • WebView优化
  • Dialog
  • 常用list选择
  • String
  • View
    • RecyclerView
    • TextView
    • Button
    • CardView
    • LinearLayoutCompat
  • AndroidManifest
  • Fragment
  • Activity
  • Service
  • ContentProvide
  • RXJava
  • MediaCode
  • OKHttp3
  • 插桩
  • 字节码Hook
  • 逻辑运算符
  • 音频识别
  • MediaPlayer 相关
  • Room
  • Camera使用
  • Gradle
  • Github
  • 资料

随手写,想哪写哪,不定期更

Glide

  1. 使用simpleTag等作为into()对象时,需设置override()属性避免加载图片过大导致OOM
  2. 对于固定区域显示大图,能截取显示的情况下可截取部分显示于控件中,使用Bitmap.createBitmap(scaleBitmap, 0, 0, w, h)进行裁剪
  3. 对于大图可用BitmapRegionDecoder进行区域性显示,参考大图展示demo
  4. 使用CustomTarget代替SimpleTarget 保证资源得以释放,在回收时回调onLoadFailed()//这个方法在target被回收时调用,如果在除了imageView以外的地方引用了imageView中的bitmap,在这里清除引用以避免崩溃
  5. 图片除开大小设置优化,可以全局设置RGB_565模式减小内存占用,注意点:1.设置之后使用createBitmp带有圆角的情况下,圆角部位会变黑,使用
CornerTransform transformation = new CornerTransform(context, dip2px(context,radius));
               //只是绘制左上角和右上角圆角
               transformation.setExceptCorner(false, false, false, false);
               MultiTransformation<Bitmap> mation = new MultiTransformation<>(new CenterCrop(), transformation);

可以设置圆角,但必须先指定宽高override(w,h);
6. 某些设备设置565之后,出现图片宽高与原宽高不一致,等比例缩小了,具体原因不明,使用 Bitmap bitmap565 = bitmap.copy(Bitmap.Config.RGB_565,true);代替

 //  Bitmap bitmap5651 = bitmapPool.get(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.RGB_565);
                    //Canvas canvas = new Canvas(bitmap565);
                    // 设置背景色,防止透明图出现黑色的情况
                    //canvas.drawColor(Color.WHITE);
                    //Paint paint=new Paint();
                    //paint.setColor(Color.WHITE);
                    //canvas.drawBitmap(bitmap, 0, 0, null);
  1. 结合Application 的onLowMemory()与registerAppStatusChangedListener 中的退到后台时调用 清除Glide内存缓存,达到减少OOM发生的次数,详情可参考 https://www.cnblogs.com/plokmju/p/7614339.html
  2. Application 中的onTrimMemory()是管理内存的重点,判断在某些内存等级下释放对应的操作或做出些加载方面的改变,比如在某个内存等级之下,使得Glide的加载都缩小0.8f,这样可达到节省内存的功能,可参考 https://www.cnblogs.com/ganchuanpu/p/7455793.html
  3. 在使用Glide.with(content).clear(tag),取消任务时,再次回到当前页可能会导致try to use recy bitmap 的崩溃;这种情况一般发送在into(new SimpleTag<>())的使用方式上,只要在加载前设置背景色可解决此问题。可参考 https://blog.csdn.net/Conan9715/article/details/117914506
  4. (高斯模糊方案)[https://www.jianshu.com/p/cf4c0efc678c]

onDestroy 延迟执行

  1. 由于onstop与onDestroy执行是在主线程IdleHandle回调下执行,如果主线程有任务未处理完,则不能立即回调onstop与onDestroy,但onReume会在延迟10s后调用退出方法作为兜底策略
  2. 解决方案一:找出阻塞线程,一般是由于动画在跳转或关闭页面是没停止注销或某些监听没有取消监听导致
  3. 解决方案二:如果阻塞的原因比较难找,可以在onPause判断isFinish()来觉得是否回收资源,提前执行要回收的代码,简单有效,这样可能存在的隐患在于onStop,onDestroy中的对象提前被回收导致空指针,还有就是getLifecycle().addObserver()绑定的对象生命周期执行的节奏不一致
  4. 参考 检查主线程执行哪些东西

资源图片

  1. 尽量加入多分辨率的图片,否则可能导致图片在高清设备上放大内存占用过多
  2. 使用imageAsset制作的ic_launcher图片外层带有一层透明层,进行裁剪显示时可能得不到想要的形状,使用其他png图片代替即可
  3. 文件下载时更新进度条可能会因为回调过于频繁而使界面发生ddos,可通过handle控制刷新的最小粒度

WebView优化

1.在使用WebView比较频繁的项目中,可提前初始化一个WebView待用,需要时直接引用,app退出时销毁,具体可参考H5实现秒开
2.销毁WebView

@Override
protected void onDestroy() {
    super.onDestroy();
    // 先从父控件中移除WebView
    mWebViewContainer.removeView(mWebView);
    mWebView.stopLoading();
    mWebView.getSettings().setJavaScriptEnabled(false);
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy();
}

  1. webView 添加js监听方法,使得webview可以调用本地方法
webView.addJavascriptInterface(new JavaObject(),"AndroidObject");
public class JavaObject{
        @JavascriptInterface
        public void clickWebBtn(String arg1,String arg2,String arg3){

Dialog

  1. dialog 中使用的editText 没有光标显示使用android:textCursorDrawable="@drawable/my_cursor" 属性进行设置
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/green" />
    <size android:width="1dp" />
</shape>

常用list选择

  1. ArrayMap

ArrayMap的使用跟HashMap是一样的。当数据结构为的时候(key的hash值为value的下标),都可以使用ArrayMap替代。
但是相较于HashMap,ArrayMap在Android中也不总是高效的。当数据量大了(数百)之后,其性能就会下降至少50%。所以,当数据量小的时候,使用ArrayMap效率还是蛮高的,HashMap 中数组的默认容量16,装载因子0.75,2倍扩容,初始化指定大小最好是2的幂,不是2的幂的会转换成2的幂, 比Math.ceil(n/0.75)大的最近的2的幂,这样在扩容时可直接进行位运算,无需转换10进制,同时减少hash碰撞(开放寻址法和拉链法解决hash碰撞)

  1. SparseArray

也是用一个
SparseIntArray:当map的结构为Map的时候使用,效率较高。
SparseBooleanArray: 当map的结构为Map的时候使用,效率较高。
SparseLongArray: 当map的结构为Map的时候使用,效率较高。
LongSparseArray: 当map的结构为Map的时候使用,效率较高。

  1. ArraySet

和ArrayMap的目的类似,用来提高HashSet的效率。使用方法跟HashSet类似

  1. String 与 list< String >快速互转 需导入.guava 工具包 compile 'com.google.guava:guava:26.0-jre'

list->String : String s=Joiner.on(“,”).join(list);
String->list: List< String > listSplitter.on(“-”).splitToList(str)

  1. ArrayList 。

内部以数组方式实现,初始长度0,有默认长容量DEFAULT_CAPACITY=10(jdk1.6,jdk1.7默认是空数组),在首次add时添加,扩容按1.5倍容量扩容;易查询,插入慢。长度初始化为默认长度就行,因为第一次扩容就是DEFAULT_CAPACITY长。

  1. Vector

与ArrayList类似,不同的是支持多线程,

  1. LinkedList

内部以循环双向链表结构实现,初始长度0,查询慢,插入快。

String

  1. 显示带有%字符的可能会报UnknownFormatConversionException,%需转义%%

View

RecyclerView

1.NestScrollView 与RecyclerView 嵌套会导致recycler不能复用,导致滑动卡顿
滑动嵌套处理
2. 分页查询由于新增数据导致重复数据解决 主要思想在于出入最后的ID给后台,后台在取值时永远取第一页,同时比较ID大小确认该页数据,避免因删除或插入导致之后的数据不准确
3. viewGroup可设置layoutAnimation动画效果,可设置子项延迟时间,子项动画效果,执行顺序等;但recyclerView这类填充视图设置后需在数据填充后手动执行startLayoutAnimation()方法

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/item_anim"
    android:animationOrder="normal"
    android:delay="20%" />

其中item_anim就是普通的set动画集

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="400"
        android:fromYDelta="-100%"
        android:fromXDelta="0"
        android:toYDelta="0"
        android:toXDelta="0"/>
    <alpha
        android:duration="400"
        android:fromAlpha="0"
        android:toAlpha="1"
        />
</set>

4.参考使用合集

TextView

  1. textView 设置了LinkMovementMethod.getInstance() 后点击事件会失效,解决方案
  2. button 自带阴影,去除阴影 android:stateListAnimator="@null"
  3. editText监听回车按钮setOnEditorActionListener,EditorInfo判断设置类别
  4. textView 使用ClickSpan进行点击设置时,会有点击冲突,解决同时触发点击事件的冲突可以去掉textView上的点击事件,在父控件上添加点击事件,同时自定义LinkMovementMethod
  5. textView 的clickspan 会有内存泄漏的问题,如果使用NoCopySpan代替ClickableSpan再8.0的版本上会触发无障碍模式的权限判定导致闪退,可尝试页面退出时回收对象
if(text instanceof Spannable){
            Spannable spannable= (Spannable) text;
            Object[] spans = spannable.getSpans(0, text.length(), Object.class);
            for (Object span: spans) {
                spannable.removeSpan(span);
            }
        }
  1. 在处理ellipsize属性失效时,可自定义ViewTreeObserver.OnGlobalLayoutListener,手动添加文本到末尾,如果设置前调用setSingleLine(false),会导致高度发生抖动,在列表中会出现滚动位置异常,可参考
public class OnGlobalLayoutListenerByEllipSize implements ViewTreeObserver.OnGlobalLayoutListener {

    private TextView mTextView;
    private int mMaxLines;  //最大行数

    public OnGlobalLayoutListenerByEllipSize(TextView textView,int maxLines){
        if(maxLines <= 0)
            throw new IllegalArgumentException("maxLines不能小于等于0");
        this.mTextView = textView;
        this.mMaxLines = maxLines;
        this.mTextView.setMaxLines(mMaxLines);
 //       this.mTextView.setSingleLine(false);
    }

    @Override
    public void onGlobalLayout() {
        mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        LogUtils.i("lineCount:"+mTextView.getLineCount()+" mMaxLine:"+mMaxLines);
        if(mTextView.getLineCount() > mMaxLines){
            int line = mTextView.getLayout().getLineEnd(mMaxLines-1);
            CharSequence truncate = "...";//定义成CharSequence类型,是为了兼容emoji表情,如果使用String类型则会造成emoji无法显示
            CharSequence text = mTextView.getText();
            try {
                text = text.subSequence(0, line -3);
            }catch (Exception e){
                truncate = "";
                text = mTextView.getText();
            }
            TextUtils.TruncateAt at = mTextView.getEllipsize();
            if(at == TextUtils.TruncateAt.START) {
                mTextView.setText(truncate);
                mTextView.append(text);
            }else if(at == TextUtils.TruncateAt.MIDDLE){
                mTextView.setText(text.subSequence(0,text.length()/2));
                mTextView.append(truncate);
                mTextView.append(text.subSequence(text.length()/2,text.length()));
            }else {
                mTextView.setText(text);
                mTextView.append(truncate);
            }
        }

    }
}
  1. editText 在dialog中使用时没有关标,需指定android:textCursorDrawable="@drawable/my_cursor",shape中设置宽度就能看到关标

Button

1.MaterialButton,提供圆角,边框,ripper 的快速实现,指定app:的相关属性即可

CardView

1.cardView 需显示指定backgroundColor ,否则在不同手机上默认的背景色不一致
2.使用setXfermode与图片组合使用时,设置模式的paint与绘制的paint不是同一个paint,设置模式的paint只针对使用的bitmap使用,该bitmap需绘制在新建的canvas中,bitmap通过eraseColor设置颜色,到画布需绘制时使用canvas.drawBitmap(mBitmap, mOldRects, mNeedDrawRect, paint);来绘制路径。
3.cardView的上位替代MaterialCardView,新增边线,点击状态,波纹等属性

LinearLayoutCompat

1.在绘制线性布局的UI时,常常需要绘制分割线,使用LinearLayoutCompat可以轻松的实现而不需要设置view的背景来实现,使用app:divider属性来设置,该属性值为drawable,并且shape中需要指定shape的高度,显示区域可通过app:showDivideers设置值middle代表两控件之间,beginning代表控件上分,end代表控件下方,app:dividerPadding代表分割线的padding,可缩进一定的位置

AndroidManifest

1. 设置最大屏占比,避免某些手机因过长而有黑边的情况
2. 解决启动黑白屏设置,设置的style与application标签中设置的style写在同一文件下,否则可能存在启动preDraw不显示问题

Fragment

  1. activity 中有fragment 被回收时,再次显示可使用 方法一:利用tag获取原有的fragment,赋值并使用 。方法二:去除原有的fragment。在activity的onCreate中不保持fragmentif (savedInstanceState != null) { savedInstanceState.putParcelable("android:support:fragments", null); },但仍然发现activity中的adapter还是fragment的数据,新建的fragment没有被使用,可以重写adapter去除
 @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        try {//清除保留的fragment
            Field mFragments = getClass().getSuperclass().getDeclaredField("mFragments");
            mFragments.setAccessible(true);
            ((ArrayList) mFragments.get(this)).clear();
            Field mSavedState = 			getClass().getSuperclass().getDeclaredField("mSavedState");
            mSavedState.setAccessible(true);
            ((ArrayList) mSavedState.get(this)).clear();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return super.instantiateItem(container, position);
    }

Activity

  1. 页面设置软件盘模式adjustResize,当页面是全屏页面(状态栏透明)时,该属性失效,解决方法

Service

  1. Service快速适配

ContentProvide

1.自定义contentProvide通过authorities定位位置,提供方设定好路径和查询实现,使用方使用对应的URI进行访问,提供方需设置export属性为true,表明允许其他应用访问,使用方需指定
或者 表明会与该包或该内容提供方可见(在android11引入的),之后才能通过ContentResolver方法访问ContentProvide中的数据

RXJava

1.使用rxjava进行任务时如果出现异常,任务取消状态下没有线程处理异常会导致事件无法下发,引发崩溃;需配置 RxJavaPlugins.setErrorHandler(throwable -> { throwable.printStackTrace(); //Trace.e("MyApplication", "MyApplication setRxJavaErrorHandler " + throwable.getMessage()); });

MediaCode

1.视频解码成帧图片,自带的MediaMetadataRetriever方法速度慢,api简单;MediaCode 速度快,但api复杂

 extractor = initMediaExtractor(file);
            mediaFormat = initMediaFormat(videoPath, extractor);
            decoder = initMediaCodec(mediaFormat);
            decoder.configure(mediaFormat, null, null, 0);
            decoder.start();
            Log.i("totalSec", "totalSec:" + totalSec+" addSp:"+addSplit);
            for (long time = 0; time < totalSec; time += addSplit) {
                //获取这一帧图片

                Bitmap bitmap = getBitmapBySec(extractor, mediaFormat, decoder, time);
                if (bitmap == null){
                    continue;
                }
            }

private static Bitmap getBitmapBySec(MediaExtractor extractor, MediaFormat mediaFormat, MediaCodec decoder, long sec) throws IOException {

        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        Bitmap bitmap = null;
        boolean sawInputEOS = false;
        boolean sawOutputEOS = false;
        boolean stopDecode = false;
        final int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
        final int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
        Log.i("getBitmapBySec", "w: " + width);
        long presentationTimeUs = -1;
        int outputBufferId;
        Image image = null;

        extractor.seekTo(sec, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);

        while (!sawOutputEOS && !stopDecode) {
            if (!sawInputEOS) {
                //Log.i("getBitmapBySec", "sawInputEOS: " + sawInputEOS);
                int inputBufferId = decoder.dequeueInputBuffer(-1);
                //Log.i("getBitmapBySec", "inputBufferId: " + inputBufferId);
                if (inputBufferId >= 0) {
                    ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferId);
                    int sampleSize = extractor.readSampleData(inputBuffer, 0);
                    if (sampleSize < 0) {
                        //Log.i("getBitmapBySec", "sampleSize<0 ");
                        decoder.queueInputBuffer(inputBufferId, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        sawInputEOS = true;
                    } else {
                        presentationTimeUs = extractor.getSampleTime();
                        //Log.i("getBitmapBySec", "presentationTimeUs: " + presentationTimeUs);
                        decoder.queueInputBuffer(inputBufferId, 0, sampleSize, presentationTimeUs, 0);
                        extractor.advance();
                    }
                }
            }
            outputBufferId = decoder.dequeueOutputBuffer(info, DEFAULT_TIMEOUT_US);
            if (outputBufferId >= 0) {
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0 | presentationTimeUs >= sec) {
                    Log.i("getBitmapBySec", "sec: " + sec);
                    sawOutputEOS = true;
                    boolean doRender = (info.size != 0);
                    if (doRender) {
                        Log.i("getBitmapBySec", "deal bitmap which at " + presentationTimeUs);
                        image = decoder.getOutputImage(outputBufferId);
                        YuvImage yuvImage = new YuvImage(YUV_420_888toNV21(image), ImageFormat.NV21, width, height, null);

                        ByteArrayOutputStream stream = new ByteArrayOutputStream();
                        yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, stream);
                        bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
                        stream.close();
                        image.close();
                    }
                }
                decoder.releaseOutputBuffer(outputBufferId, true);
            }
        }

        return bitmap;
    }

OKHttp3

1.接口缓存,分两种:
有网状态,添加addNetworkInterceptor()拦截器
设置max-age用于在一定时间内重复调用取缓存数据,超过时间重新获取。

public class CacheOnlineInterceptor implements Interceptor {
    private CacheOnlineInterceptor(){}
    private static CacheOnlineInterceptor cacheInterceptor;
    public static CacheOnlineInterceptor getInstance(){
        if(cacheInterceptor==null){
            cacheInterceptor=new CacheOnlineInterceptor();
        }
        return cacheInterceptor;
    }
    @NonNull
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request request=chain.request();
        Response response=chain.proceed(request);
        return response.newBuilder().header("Cache-Control","public,max-age=30")
                .removeHeader("Pragma")
                .build();
    }
}

无网状态 添加应用拦截器addInterceptor()
可以设置FORCE_CACHE策略,直接取缓存,没有就报504错误;也可以设置max-stale缓存失效时间,超过时间不使用缓存。

public class CacheInterceptor implements Interceptor {
    private CacheInterceptor(){}
    private static CacheInterceptor cacheInterceptor;
    public static CacheInterceptor getInstance(){
        if(cacheInterceptor==null){
            cacheInterceptor=new CacheInterceptor();
        }
        return cacheInterceptor;
    }
    @NonNull
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request request = chain.request();
        Request.Builder requestBuilder = request.newBuilder();


        if (!NetworkUtils.isConnected()) {
            Log.i("testHttpUtils","no netWork");
           // requestBuilder.header("Cache-Control","public,only-if-cached,max-stale=600");
            requestBuilder.cacheControl(CacheControl.FORCE_CACHE); // 直接使用缓存
        }
        return chain.proceed(requestBuilder.build());
    }
}

需注意缓存接口在调用前后完全一致才会其作用,如果入参在有网和没网时候有不同,比如传了网络状态或时间戳,那接口不一致就无法使用缓存的数据。且OKHttp只对Get请求有缓存,POST请求没有缓存
2. 只对指定接口进行缓存时,首先接口处设置对应的head 如

 @Headers("Cache-Control: public, max-age=" + 24 * 3600)
 @GET("url")
 Observable<?> queryInfo(@Query("userName") String userName);

这样在拦截器处读取String cacheControl = request.cacheControl().toString();头文件中的值判断是否需要使用缓存
3. 在添加网络拦截器时可去除可变参数,请求时判断是否有网,无网就不添加该参数,这样可以使得无网情况下获取到对应缓存而不受可变参数的影响;对于一些特殊参数:比如碰到类似传入时间戳的接口,可以将时间在一定时间内向下取整,保证一段时间内的时间戳相同,在有网状态下可以规避调因参数的不同而无法使用缓存的情况,该方法与去除参数缓存方案不可共存。

HttpUrl newUrl=chain.request().url().newBuilder().removeAllQueryParameters("timestamp").build();
        Request newRequest=chain.request().newBuilder().url(newUrl).build();
        return response.newBuilder().request(newRequest).header("Cache-Control","public,max-age=30")
                .removeHeader("Pragma")
                .build();

插桩

  1. 资料:https://www.yuque.com/docs/share/b49a3274-17d9-4c87-815a-4a13ab72e4e0
  2. 资料:https://mp.weixin.qq.com/s/dQjsxduUiNrMYH2xhhpmQA
  3. 方案 AspectJ,Javassist,ASM,booster,matrix,byteX

字节码Hook

1.lancet 使用可参考:https://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650840653&idx=1&sn=b862759352e0f8bb7c2675006d2e63af&chksm=80b74ad3b7c0c3c547f18e2a50571695b0f9568e6b9c5d995d468d9f140410cd4b9d70b3ad59&scene=21#wechat_redirect

逻辑运算符

合理运用运算符 &、|、^、>>、<< 可以快捷方便的进行一些运算

符号 规则 应用
& 都为1结果为1 1.清零:a&0=0
2.取一个数的指定位(二进制下):a&0111 就是指定后三位
3.判断奇偶:a&1==0 为偶
| 有一个为1结果为1 1.一个数某些位设置为1(二进制下):a
^ 相同为0不同为1 1.指定位翻转:a^0111 就是将后三位翻转
2.与0异或结果为本身:a^0=a
3.交换两值:a=a ^ b;b=a ^ b;a=a ^ b
4.与自身异或结果为0
~ 取反 ~1=0; ~0=1
>> 向右移动若干位,正数补0负数补1 1.a>>n = a除于 2的n次方
<< 向左移动若干位,补0 1.a<

音频识别

音频识别框架:https://juejin.cn/post/7268960029272473635

MediaPlayer 相关

  1. AcousticEchoCanceler 继承AudioEffect 回音消除
    简称AEC在播放和录制是同时进行的场景下,消除播放的音频,不录制进去
    先判断设备是否支持AcousticEchoCanceler.isAvailable()使用
    添加权限android.permission.RECORD_AUDIO
    AcousticEchoCanceler.create(audioRecord.getSessionId()).setEnabled(true),这样就开启了播放的回音消除,之后播放时将这个ID设置给播放器,比如audioTrack=new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, mRecorderBufferSize * 2 , AudioTrack.MODE_STREAM, audioSessionId);
    sessionId 也可以尝试通过AudioSystem audioSystem = AudioSystem.getAudioSystem( AudioSystem.LOCATOR_PROTOCOL_AUDIORECORD);audioSession = audioSystem.getAudioSessionId();获取
    最后释放对象 aec.setEnable(false),aec.release();
  2. AudioEffect
    AudioEffect是音频播放的进行音效控制的基础类,一般使用他的派生类
  3. Equalizer 均衡器(控制某一频率的声响大小)、
Equalizer equalizer = new Equalizer(0, mediaPlayer.getAudioSessionId());
equalizer.setEnabled(true);

//获取均衡器引擎支持的频段数
short bands = equalizer.getNumberOfBands();
        
//获取最大和最小增益
final short minEQLevel = equalizer.getBandLevelRange()[0];
final short maxEQLevel = equalizer.getBandLevelRange()[1];
for (short i = 0; i < bands; i++) {
    final short band = i;
    //获取当前频段的中心频率,分别为:60Hz,230Hz,910Hz,3600Hz,14000Hz
    int currentFreq = equalizer.getCenterFreq(band);
    //获取给定均衡器频段的增益
    short level = equalizer.getBandLevel(band);
    Log.d("Equalizer","currentFreq is: " + currentFreq + ", band level is: " + level);
    //为给定的均衡器频带设置增益值
    equalizer.setBandLevel(band,xx);
}

short presets= equalizer.getNumberOfPresets();//增益参数
//获取系统预设的增益
for (short i = 0; i < presets; i++) {
  Log.d("presets",equalizer.getPresetName(i));
}
//Normal、Classical、Dance、Flat、Folk、Heavy Metal、Hip hop、Jazz、Pop、Rock。
equalizer.usePreset(i);//设置参数

  1. Virtualizer 环绕音(让声音产生空间感)、
Virtualizer mVirtualizer= new Virtualizer (0, mMediaPlayer.getAudioSessionId()); //优先级为0
mVirtualizer.setEnabled(true);
if (mVirtualizer.getStrengthSupported())
{
    short strength = mVirtualizer.getRoundedStrength();
    mVirtualizer.setStrength((short)strength);//设置力度 力度越大声音越远
}
getRoundedStrength() :获取特效力度,特效力度值在01000间变化。
  1. BassBoost重低音控制器(增强低音强度)、
    和简单均衡器类似,但只作用于低频段
BassBoost bassBoost = new BassBoost(0,mediaPlayer.getAudioSessionId());
bassBoost.setEnabled(true);
if (bassBoost.getStrengthSupported()){
    bassBoost.setStrength((short) 100);
}
  1. PresetReverb 混响(一般用于音乐,反射叠加产生的音效如流行,爵士等)、
PresetReverb presetReverb = new PresetReverb(0,mediaPlayer.getAudioSessionId());
presetReverb.setEnabled(true);
presetReverb.setPreset(PresetReverb.PRESET_LARGEROOM);//0-6空间逐渐增大
  1. EnvironmentalReverb 环境混响(一般用于游戏,如马路,大厅,室内等)
    setDecayHFRatio:设置高频到中频衰减比率。范围是[100, 2000] ,如果设为1000,则全部衰减相同。
    setDecayTime:中频混响衰减时间。[100, 20000]
    setDensity:在后期混响衰减,控制模态密度的值。[0, 1000]
    setDiffusion:在后期混响衰减,控制回声密度的值。 [0, 1000]
    setReflectionsDelay:初始反射延迟时间。[0, 300]
    setReflectionsLevel:对于环境效果的早期反射等级。[-9000, 1000]
    setReverbDelay:先对于初始反射的后期混响延迟时间。 [0, 100]
    setReverbLevel:相对于环境效果的后期混响等级。[-9000, 2000]
    setRoomHFLevel:相对于高频环境效果等级。 [-9000, 0]
    setRoomLevel:相对于低频环境效果等级。[-9000, 0]
  2. DynamicsProcessing
    android9.0后构建基于通道的音效,包括均衡器,压缩器,限制器,通过DynamicsProcessing可以实现上述的所有功能
//创建对象
int audioSessionId = mediaPlayer.getAudioSessionId();
DynamicsProcessing.Config.Builder builder = new DynamicsProcessing.Config.Builder(
                        0,//variant
                        1,//channelCount
                        true,//preEqInUse
                        10,//preEqBandCount
                        true,//mbcInUse
                        10,//mbcBandCount
                        true,//postEqInUse
                        10,//postEqBandCount
                        true//limiterInUse
);
DynamicsProcessing mDynamicsProcessing = new DynamicsProcessing(0, audioSessionId, builder.build());
mDynamicsProcessing.setEnabled(true);

//创建均衡器对象
//创建用于调节的10个频段
private static final int[] bandVal = {31, 62, 125, 250, 500, 1000, 2000, 4000, 8000, 16000};
private static final int maxBandCount = bandVal.length;

DynamicsProcessing.Eq mEq = new DynamicsProcessing.Eq(true, true, maxBandCount);
mEq.setEnabled(true);

for (int i = 0; i < maxBandCount; i++) {
    mEq.getBand(i).setCutoffFrequency(bandVal[i]);//设置此频段将处理的最高频率数(以 Hz 为单位)
}
//设置压缩前的均衡器给全频道
mDynamicsProcessing.setPreEqAllChannelsTo(mEq);
//设置压缩前的均衡器给指定频道
//public static final int CHANNEL_1 = 0;
//public static final int CHANNEL_2 = 1;
//mDynamicsProcessing.setPreEqByChannelIndex(CHANNEL_1, mEq);

//设置压缩后的均衡器给全频道
//mDynamicsProcessing.setPostEqAllChannelsTo(mEq);
//设置压缩后的均衡器给指定频道
//public static final int CHANNEL_1 = 0;
//public static final int CHANNEL_2 = 1;
//mDynamicsProcessing.setPostEqByChannelIndex(CHANNEL_1, mEq);

//创建压缩器对象
DynamicsProcessing.Mbc mDynamicsProcessingMbc = new DynamicsProcessing.Mbc(true, true, maxBandCount);
mDynamicsProcessingMbc.setEnabled(true);
for (int i = 0; i < maxBandCount; i++) {
    mDynamicsProcessingMbc.getBand(i).setCutoffFrequency(bandVal[i]);//设置此频段将处理的最高频率数(以 Hz 为单位)
}

//设置多频段压缩器给全频道
mDynamicsProcessing.setMbcAllChannelsTo(mDynamicsProcessingMbc);
//设置多频段压缩器给指定频道
//mDynamicsProcessing.getMbcBandByChannelIndex(CHANNEL_1, mDynamicsProcessingMbc);

//创建限制器对象
//Limiter构造参数
private static final boolean LIMITER_DEFAULT_IN_USE = true;//如果将使用 MBC 阶段,则为 true,否则为 false。
private static final boolean LIMITER_DEFAULT_ENABLED = true;//如果启用/禁用 MBC 阶段,则为 true。这可以在效果运行时更改
private static final int LIMITER_DEFAULT_LINK_GROUP = 0;//分配给此限制器的组的索引。只有共享相同 linkGroup 索引的限制器才会一起做出反应。
private static final float LIMITER_DEFAULT_ATTACK_TIME = 1; // 限制器压缩器的启动时间,以毫秒 (ms) 为单位
private static final float LIMITER_DEFAULT_RELEASE_TIME = 60; //限制器压缩器的释放时间,以毫秒 (ms) 为单位
private static final float LIMITER_DEFAULT_RATIO = 10; // 限制器压缩比 (N:1)(输入:输出)
private static final float LIMITER_DEFAULT_THRESHOLD = -2; // 限幅压缩器阈值以分贝 (dB) 为单位,从 0 dB 满量程 (dBFS) 开始测量。
private static final float LIMITER_DEFAULT_POST_GAIN = 0; // 压缩后应用于信号的增益。

DynamicsProcessing.Limiter mDynamicsProcessingLimiter = new DynamicsProcessing.Limiter(LIMITER_DEFAULT_IN_USE, LIMITER_DEFAULT_ENABLED, LIMITER_DEFAULT_LINK_GROUP, LIMITER_DEFAULT_ATTACK_TIME, LIMITER_DEFAULT_RELEASE_TIME, LIMITER_DEFAULT_RATIO, LIMITER_DEFAULT_THRESHOLD, LIMITER_DEFAULT_POST_GAIN);
mDynamicsProcessingLimiter.setEnabled(true);

//设置限制器给全频道
mDynamicsProcessing.setLimiterAllChannelsTo(mDynamicsProcessingLimiter);
//设置限制器给指定频道
//mDynamicsProcessing.setLimiterByChannelIndex(CHANNEL_1, mDynamicsProcessingLimiter);

//调节输入增益
mDynamicsProcessing.setInputGainAllChannelsTo(value);

//调节均衡器
//根据调节的频段在bandVal数组中的索引bandIndex和调整后的值gain来调节均衡器
mEq.getBand(bandIndex).setGain(gain);
//设置压缩前的均衡器频段增益给全频道
mDynamicsProcessing.setPreEqBandAllChannelsTo(bandIndex, mEq.getBand(bandIndex));
//设置压缩前的均衡器频段增益给指定频道
//mDynamicsProcessing.setPreEqBandByChannelIndex(CHANNEL_1, bandIndex, mEq.getBand(bandIndex));

//设置压缩后的均衡器频段增益给全频道
//mDynamicsProcessing.setPostEqBandAllChannelsTo(bandIndex, mEq.getBand(bandIndex));
//设置压缩后的均衡器频段增益给指定频道
//mDynamicsProcessing.setPostEqBandByChannelIndex(CHANNEL_1, bandIndex, mEq.getBand(bandIndex));

//调节压缩器
//设置在压缩之前应用于信号的增益,以分贝 (dB) 为单位测量,其中 0 dB 表示没有电平变化。
mDynamicsProcessingMbc.getBand(bandIndex).setPreGain(gain);
//设置给全频道
mDynamicsProcessing.setMbcBandAllChannelsTo(bandIndex, mDynamicsProcessingMbc.getBand(bandIndex));
//设置给指定频道
//mDynamicsProcessing.setMbcBandByChannelIndex(CHANNEL_1, bandIndex, mDynamicsProcessingMbc.getBand(bandIndex));

//设置在压缩之后应用于信号的增益,以分贝 (dB) 为单位测量,其中 0 dB 表示没有电平变化。
//mDynamicsProcessingMbc.getBand(bandIndex).setPostGain(gain);
//设置给全频道
//mDynamicsProcessing.setMbcBandAllChannelsTo(bandIndex, mDynamicsProcessingMbc.getBand(bandIndex));
//设置给指定频道
//mDynamicsProcessing.setMbcBandByChannelIndex(CHANNEL_1, bandIndex, mDynamicsProcessingMbc.getBand(bandIndex));

//销毁
 mDynamicsProcessing.setEnabled(false);
    mDynamicsProcessing.release();
    mDynamicsProcessing = null;

Room

  1. @TypeConverters 用于数据类型转换,在不同地方作用域不同,主要处理非基本数据类型在数据库中的映射类型 参考该文章
class Converters {

    @TypeConverter

    fun fromDate(date: Date): Long {

        return date.time

    }

    @TypeConverter

    fun toDate(timestamp: Long): Date {

        return Date(timestamp)

    }

    @TypeConverter

    fun fromList(list: List<String>?): String? {

        return list?.joinToString(",")

    }

    @TypeConverter

    fun toList(string: String?): List<String>? {

        return string?.split(",")

    }

}

@Entity(tableName = "user")

@TypeConverters(Converters::class)

data class User(

    @PrimaryKey val id: Int,

    val name: String,

    val birthday: Date

    @TypeConverters(HobbiesConverter::class) val hobbies: List<String>

)

@Database(entities = [User::class], version = 1)

@TypeConverters(Converters::class)

abstract class AppDatabase : RoomDatabase() {

    // ...

}

@Dao

@TypeConverters(Converters::class)

interface UserDao {

    @Query("SELECT * FROM user")

    fun getAll(): List<User>

}
  1. @Relation 用于声明两个实体间关系,这样在搜索时会根据设定的关系返回关联对象,用于多表间数据结构的嵌套返回
@Entity(tableName = "users")

data class User(

    @PrimaryKey val id: Int,

    val name: String,

    val email: String

)

@Entity(tableName = "books")

data class Book(

    @PrimaryKey val id: Int,

    val title: String,

    val author: String,

    val userId: Int

)

data class UserWithBooks(

    @Embedded val user: User,

    @Relation(

        parentColumn = "id",

        entityColumn = "userId"

    )

    val books: List<Book>

)

//---------------查询
@Query("SELECT * FROM users WHERE id = :id")
fun getUserWithBooks(id: Int): UserWithBooks
  1. @Embedded 指定嵌入式对象,可以简化对象构成,构成对象可添加prefix属性对字段添加前缀
@Entity(tableName = "users")

@Entity(tableName = "users")

data class User(

    @PrimaryKey val id: Int,

    val name: String,

    @Embedded(prefix = "home_") val homeAddress: Address,

    @Embedded(prefix = "work_") val workAddress: Address

)
data class Address(

    val street: String,

    val city: String,

    val state: String,

    val zip: String

)

Camera使用

目前官方api主要分三类camera,camera2,cameraX,用法和兼容性稍有不同
CameraApi 介绍与使用

Gradle

Gradle资料

Github

访问github时经常访问速度过慢而导致加载失败,为解决这一问题,可以先找到github.com所对应的IP,将域名与IP的映射添加到本地C:\Windows\System32\drivers\etc\host 文件中,空格分割:

20.205.243.166 github.com
185.199.108.154 github.githubassets.com
185.199.110.154 github.githubassets.com
185.199.109.154 github.githubassets.com
185.199.111.154 github.githubassets.com

资料

锁,OKhttp,list,Glide 源码分析

你可能感兴趣的:(android,java)