android 开发艺术探索-笔记

2.IPC机制

介绍Android中的序列化机制和Binder,Bundle、文件共享、AIDL、Messager、ContentProvider、socket,还有Binder连接池的概念。

2.1 IPC简介

IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。按照操作系统中的描述,线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。

IPC不是Android中所独有的,任何一个操作系统都需要有相应的IPC机制,比如Windows上可以通过剪贴板、管道和邮槽等来进行进程间通信;Linux上可以通过命名管道、共享内容、信号量等来进行进程间通信。

在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。除了Binder,Android还支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然同一个设备上的两个进程通过Socket通信自然也是可以的

2.2 Android中的多进程模式

给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest中指定android:process属性,就可以完成多进程。

<activity
android:name="com.ryg.chapter_2.SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process=":remote" />
<activity
android:name="com.ryg.chapter_2.ThirdActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process="com.ryg.chapter_2.remote" />

两个方面:
1、“:”的含义是指要在当前的进程名前面附加上当前的包名,这是一种简写的方法,对于SecondActivity来说,它完整的进程名为com.ryg.chapter_2:remote,
2、以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。这里要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。

一般来说,使用多进程会造成如下几方面的问题:
(1)静态成员和单例模式完全失效。
(2)线程同步机制完全失效。
(3)SharedPreferences的可靠性下降。
(4)Application会多次创建。

2.3 IPC基础概念介绍

2.3.1 Serializable接口

Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。

serialVersionUID的作用
serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。serialVersionUID的详细工作机制是:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;

2.3.2 Parcelable接口

Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。而Parcelable是Android中的序列化方式,因此更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高,这是Android推荐的序列化方式,因此我们要首选Parcelable。Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备
中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议大家使用Serializable。以上就是Parcelable和Serializable和
区别

2.3.3 Binder

Binder是Android中的一个类,它继承了IBinder接口。
从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;

从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁

从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

在这里插入图片描述

Book.java是一个表示图书信息的类,它实现了Parcelable接口。
Book.aidl是Book类在AIDL中的声明。
IBook.aidl是我们定义的一个接口,里面有两个方法:getBookList和addBook,
注意:不同的后缀名,放入的文件夹不一样。

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

...
}
// Book.aidl
package com.example.aidldemo;
parcelable Book;


// IBook.aidl
package com.example.aidldemo;

import com.example.aidldemo.Book;
interface IBook {

     List<Book> getBookList();
     void addBook(in Book book);
}

然后编译生成一个IBook.java 接口类。路径为build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/com/example/aidldemo
在这里插入图片描述
DESCRIPTOR

Binder的唯一标识,一般用当前Binder的类名表示,比如本例中的“com.ryg.chapter_2.aidl.IBookManager”。

asInterface(android.os.IBinder obj)

用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象

asBinder
此方法用于返回当前Binder对象。

onTransact
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型为public Boolean onTransact(int code,android.os.Parcel data,android.os.Parcel reply,int flags)。服务端通过
code可以确定客户端所请求的目标方法是什么,接着
从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向
reply中写入返回值(如果目标方法有返回值的话),
onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
Proxy#getBookList
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。
Proxy#addBook
这个方法运行在客户端,它的执行过程和getBookList是一样的,addBook没有返回值,所以它不需要从_reply中取出返回值。

AIDL文件并不是实现Binder的必需品,AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具,仅此而已。
linkToDeath和unlinkToDeath
通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接.

 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
            if (mRemoteBookManager == null)
                return;
            mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mRemoteBookManager = null;
            // TODO:这里重新绑定远程Service
        }
    };

2.3.4 Messager

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL.

3.View 事件体系

3.1 view 位置

有左上角和右下角来确认view的位置 top left right botttom,这些都是相对父容器来说。
从Android 3.0 ,view增加了x ,y translationx,translationy ,x和y是左上角的坐标,而translationx,translationy 是左上角相对于父容器的偏移量。
注意:view平移的时候,top 、left是原始左上角的位置,其值并不会发生变化,变化的是x.y translationx和translationy这四个参数。

3.2 MotionEvent

getX 、getY是当前点击的位置相对于当前view的坐标。
getRawX 、 getRawY是相对于屏幕左上角的坐标。

3.3 TouchSlop

touchSlop是系统能够识别出被认为滑动的最小距离。

ViewConfiguration.get(getContent()).getScaledTouchSlop();

源码位置:frameworks/base/core/res/res/values/config.xml

<dimen name="config_viewconfigurationTouchSlop">8dp</dimen>

3.4 VelocityTracker 速度追踪

速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。

VelocityTracker velocityTracker = VelocityTracker.obtain(); velocityTracker.addMovement(event);

velocityTracker.computeCurrentVelocity(1000); 
int xVelocity = (int) velocityTracker.getXVelocity(); 
int yVelocity = (int) velocityTracker.getYVelocity();

第一点,获取速度之前必须先计算速度,即getXVelocity和getYVelocity这两个方法的前面必须要调用computeCurrentVelocity 方法;
第二点,这里的速度是指一段时间内手指所滑过的像素数,比如将时间间隔设为1000ms时,在1s内,手指在水平方向从左向右滑过100像素,那么水 平速度就是100。注意速度可以为负数,当手指从右往左滑动时,水平方向速度即为负值

3.5 GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为

GestureDetector mGestureDetector = new GestureDetector(this); 
//解决长按屏幕后无法拖动的现象
 mGestureDetector.setIsLongpressEnabled(false);
  @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean consume = mGestureDetector.onTouchEvent(event);

        Log.d(TAG, "onTouchEvent: ");
        return consume;
    }

    @Override
    public boolean onDown(MotionEvent e) {
        Log.d(TAG, "onDown: ");
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {

        Log.d(TAG, "onShowPress: ");
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        Log.d(TAG, "onSingleTapUp: ");
        return false;
    }

    /**
     * 手指按下屏幕并滑动
     * @param e1
     * @param e2
     * @param distanceX
     * @param distanceY
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.d(TAG, "onScroll: ");
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        Log.d(TAG, "onLongPress: ");

    }

    private static final String TAG = "MainActivity";

    /**
     * 快速滑动,down --move  --up
     * @param e1
     * @param e2
     * @param velocityX
     * @param velocityY
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.d(TAG, "onFling: ");
        return false;
    }

3.6 view 的滑动

1.view 提供的scrollTo/ scrollBy方法实现滑动

scrollTo()是绝对滚动,scrollby() 是相对滑动,是增量滑动。两者只能改变view 内容的位置,不能改变view在布局中的位置。

2.通过动画给view施加平移效果来实现滑动

3.通过改变view的layoutprams使得view重新布局从而实现滑动。

4.弹性滑动Scroller

  Scroller scroller = new Scroller(getContext());
    
    private void smoothScrollTo(int destX,int destY){
        int scrollX = getScrollX();
        int deltaX = destX = scrollX;
        
        scroller.startScroll(scrollX,0,deltaX,0,1000);
        
        invalidate();
    }

    @Override
    public void computeScroll() {
        
        if (scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            postInvalidate();
        }
    }

scroller内部没有做什么,只是保存传入的几个参数。

注意这里的滑动使用的scrollTo,所以滑动的是view的内容而非view本身位置的改变。

// startX startY 滑动的起点,dx dy 表示要滑动的距离,duration 滑动时间

 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
    }

那么Scroller到底是如何到底滑动?

startScroll方法的invalidate(). invalidate()会导致view重绘,在view的draw方法会去调用computeScroll方法,computeScroll方法在view中是一个空实现,需要我们自己实现。
原理:当view重绘后在draw方法中调用computeScroll,而computeScroll又会向Scroller获取当前的scrollX scrollY,然后通过scrollTo方法实现滑动,接着调用postInvalidate进行第二次重绘,这次重绘和第一次一样,还会导致computeScroll被调用,继续向Scroller获取当前的scrollX scrollY,通过调用scrollTo方法滑动到新的位置,如此反复。

scroller的computeScrollOffset方法实现

 /**
     * Call this when you want to know the new location.  If it returns true,
     * the animation is not yet finished.
     */ 
    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }

                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
                
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);

                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }

                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

4.事件分发机制

public boolean dispatchTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(motionEvent ev)
public boolean onTouchEvent(MotionEvent ev);

伪代码:

public boolean dispathcTouchEvent(MotionEvent ev){
	boolean consume = false;
	if(onInterceptTouchEvent(ev)){
		consume = onTouchEvent(ev);
	}else{
		consume = child.dispatchTouchEvent(ev);
	}
	return consume;
}

8 windowManager

https://blog.csdn.net/chentaishan/article/details/122435355

11.Android线程

1.AsyncTask

AsyncTask是一个抽象的泛型类,它提供了Params、Progress和Result这三个泛型参数,其中Params表示参数的类型,Progress表示后台任务的执行进度的类型,而Result则表示后 台任务的返回结果的类型,如果AsyncTask确实不需要传递具体的参数,那么这三个泛型参数可以用Void来代替。

AsyncTask这个类的声明如下所示。

public abstract class AsyncTask<Params,Progress,Result>

AsyncTask提供了4个核心方法,它们的含义如下所示。
(1)onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。
(2)doInBackground(Params…params),在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务 的进度,publishProgress方法会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute方法。
(3)onProgressUpdate(Progress…values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。
(4)onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中result参数是后台任务的返回值,即doInBackground的返回值。
内部实现原理:
https://blog.csdn.net/chentaishan/article/details/104335445

2.HandlerThread

3.IntentService

4.Android 线程池

13 综合技术

通过CrashHandler来监视应用的crash信息,给程序设置一个CrashHandler,这样当程序crash时就会调用CrashHandler的uncaughtException方法。

在Android中,有一个限制,那就是整个应用的方法数不能超过65536,否则就会出现编译错误,并且程序也无法成功地安装到手机上。当项目日益庞大后这个问题就比较容易遇到,Google提供了multidex方案专门用于解决这个问题,通过将一个dex文件拆分为多个dex文件来避免单个dex文件方法数越界的问
题。
方法数越界的另一种解决方案是动态加载。动态加载可以直接加载一个dex形式的文件,将部分代码打包到一个单独的dex文件中(也可以是dex格式的jar
或者apk),并在程序运行时根据需要去动态加载dex中的类,这种方式既可以解决缓解方法数越界的问题,也可以为程序提供按需加载的特性,同时这还为
应用按模块更新提供了可能性。

13.1 使用CrashHandler来获取应用的crash信息

Android应用不可避免地会发生crash,也称之为崩溃, Android提供了处理这类问题的方法。

//Thread内部的uncaughtException方法
 @FunctionalInterface
 public interface UncaughtExceptionHandler {
     void uncaughtException(Thread var1, Throwable var2);
 }

public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh{
    defaultUncaughtExceptionHandler = eh;
}

设置系统的默认异常处理器, 解决上面所提到的crash问题。当crash发生的时候,系统就 会回调UncaughtExceptionHandler的uncaughtException方法,在uncaughtException方法中就可以获取到异常信息。

首先需要实现一个UncaughtExceptionHandler对象,在它的uncaughtException方法中获取异 常信息并将其存储在SD卡中或者上传到服务器供开发人员分析,然后调用Thread的setDefaultUncaught-ExceptionHandler方法将它设置为线程默认的异常处理器, 由于默认异常处理器是Thread类的静态成员,因此它的作用对象是当前进程的所有线程。

public class CrashHandler implements UncaughtExceptionHandler {
    private static final String TAG = "CrashHandler";
    private static final boolean DEBUG = true;

    private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/CrashTest/log/";
    private static final String FILE_NAME = "crash";
    private static final String FILE_NAME_SUFFIX = ".trace";

    private static CrashHandler sInstance = new CrashHandler();
    private UncaughtExceptionHandler mDefaultCrashHandler;
    private Context mContext;

    private CrashHandler() {
    }

    public static CrashHandler getInstance() {
        return sInstance;
    }

    public void init(Context context) {
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        mContext = context.getApplicationContext();
    }

    /**
     * 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用#uncaughtException方法
     * thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息。
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        try {
            //导出异常信息到SD卡中
            dumpExceptionToSDCard(ex);
            uploadExceptionToServer();
            //这里可以通过网络上传异常信息到服务器,便于开发人员分析日志从而解决bug
        } catch (IOException e) {
            e.printStackTrace();
        }

        ex.printStackTrace();

        //如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己
        if (mDefaultCrashHandler != null) {
            mDefaultCrashHandler.uncaughtException(thread, ex);
        } else {
            Process.killProcess(Process.myPid());
        }

    }

    private void dumpExceptionToSDCard(Throwable ex) throws IOException {
        //如果SD卡不存在或无法使用,则无法把异常信息写入SD卡
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            if (DEBUG) {
                Log.w(TAG, "sdcard unmounted,skip dump exception");
                return;
            }
        }

        File dir = new File(PATH);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        long current = System.currentTimeMillis();
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
        File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);

        try {
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
            pw.println(time);
            dumpPhoneInfo(pw);
            pw.println();
            ex.printStackTrace(pw);
            pw.close();
        } catch (Exception e) {
            Log.e(TAG, "dump crash info failed");
        }
  }

}

13.2 multidex解决方法数越界

在Android中单个dex文件所能够包含的最大方法数为65536,这包含Android FrameWork、依赖的jar包以及应用本身的代码中的所有方法。

com.android.dex.DexIndexOverflowException: method ID not in [0,0xffff]: 65536

另外一种情况有所不同,有时候方法数并没有达到65536,并且编译器也正常地完成了编译工作,但是应用在低版本手机安装时异常中止,异常信息如 下:

E/dalvikvm: Optimization failed E/installd: dexopt failed on '/data/dalvik-cache/data@app@com.ryg.
multidextest-2.apk@classes.dex' res = 65280

其实是这样的,dexopt是一个程序,应用在安装时,系统会通过dexopt来优化dex文件,在优化过程中dexopt采用一个固定大小 的缓冲区来存储应用中所有方法的信息,这个缓冲区就是LinearAlloc。LinearAlloc缓冲区在新版本的Android系统中其大小是8MB或者16MB,但是在Android 2.2 和2.3中却只有5MB,当待安装的apk中的方法数比较多时,尽管它还没有达到65536这个上限,但是它的存储空间仍然有可能超出5MB,这种情况下dexopt程 序就会报错,从而导致安装失败。

解决:

  • 删除无用的代码和第三方库
  • 采用插件化的机制来动态加载部分dex,通过将一个dex拆分成两个 或多个dex,这就在一定程度上解决了方法数越界的问题。但是插件化是一套重量级的技术方案,并且其兼容性问题往往较多
  • multidex的解决方案

在Android 5.0以前使用multidex需要引入Google提供的android-support-multidex.jar这个jar包,这个jar包可以在Android SDK目录下的 extras/android/support/multidex/library/ libs下面找到。从Android 5.0开始,Android默认支持了multidex

13.3 指定主dex文件中所 要包含的类

在module下 build.gradle文件里,添加;

afterEvaluate {
    println "afterEvaluate"
    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
    	//引入配置文件,配置哪些类可以打包到dex文件里
        def listFile = project.rootDir.absolutePath + '/app/maindexlist.txt'
        println "root dir:" + project.rootDir.absolutePath
        println "dex task found: " + dx.name
        if (dx.additionalParameters == null) {
            dx.additionalParameters = []
        }
        dx.additionalParameters += '--multi-dex'
        dx.additionalParameters += '--main-dex-list=' + listFile
        dx.additionalParameters += '--minimal-main-dex'
    }
}

maindexlist.txt文件

com/ryg/multidextest/TestApplication.class
com/ryg/multidextest/MainActivity.class

// multidex
android/support/multidex/MultiDex.class
android/support/multidex/MultiDexApplication.class
android/support/multidex/MultiDexExtractor.class
android/support/multidex/MultiDexExtractor$1.class
android/support/multidex/MultiDex$V4.class
android/support/multidex/MultiDex$V14.class
android/support/multidex/MultiDex$V19.class
android/support/multidex/ZipUtil.class
android/support/multidex/ZipUtil$CentralDirectory.class

15.性能优化

布局优化

尽量减少布局文件的层级

标签

将一个指定的布局文件加载到当前的布局文件中

标签

标签一般和标签一起使用从而减少布局的层级

ViewStub

ViewStub继承了View,它非常轻量级且宽/高都是0,因此它本身不参与任何的布局和绘制过程。

<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/layout_network_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);

View importPanel = ((ViewStub)findViewById(R.id.stub_import)).inflate();

当ViewStub通过setVisibility或者inflate方法加载后,ViewStub就会被它内部的布局替换掉,这个时候ViewStub就不再是整个布局结构中的一部分了。另外,
目前ViewStub还不支持标签。

绘制优化

绘制优化是指View的onDraw方法要避免执行大量的操作
,onDraw中不要创建新的局部对象,这是因为onDraw方法可能会被频繁调用
onDraw方法中不要做耗时的任务,也不能执行成千上万次的循环操作
View的绘制帧率保证60fps是最佳的,这就要求每帧的绘制时间
不超过16ms(16ms = 1000 / 60),

内存泄漏优化

单例模式导致的内存泄露
属性动画导致的内存泄露

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值