一个字:删!!删不了就尽可能小。java
图片:apk里面的资源图片 压缩图片 svg图片:一些图片的描述,牺牲CPU的计算能力的,节省空间。 使用的原则:简单的图标。 webp:谷歌如今很是提倡的使用。保存图片比较小。 VP8派生而来的。webp的无损压缩比PNG文件小45%左右,即便PNG进过其余的压缩工具压缩后, 任然能够减少到PNG的28%。android
Facebook在用、腾讯、淘宝。 缺点:加载相比于PNG要慢不少。 可是配置比较高。 工具:isparta.github.io/git
好比:emoji表情、换肤 动态下载的资源。 一些模块的插件化动态添加。github
1)检测没有用的布局 删除 2)未使用到的资源 好比 图片 ---删除 3)建议String.xml有一些没有用到的字符。web
7zZip工具的使用。算法
让apk变小。为何? 1)能够删除注释和不用的代码。 2)将java文件名改为短名a.java,b.java 3)方法名等 CommonUtil.getDisplayMetrix();--》a.a() 4) shrinkResource 去除无用资源shell
系统编译完成apk文件之后: 映射关系:res/drawable/ic_launcher.png ----- > 0x7f020000数据库
再作“混淆”:要实现将res/drawable/ic_launcher.png图片改为a.png 将drawable,String,layout的名字继续缩减 好比:R.string.description--->R.string.a res/drawable/ic_launcher.png图片改为a.pngexpress
还能够更加夸张 res/drawable--->r/d res/value-->r/v res/drawable/ic_launcher.png图片改为r/d/a.pngapache
读取resources.arsc二进制文件,而后修改某一段一段的字节。 有一段叫作:res/drawable/ic_launcher.png 在本身数组当中的第800位-810位 将这一段第800位-810位替换成改为r/d/a.png 的字节码。
args参数: demo.apk -config config.xml -7zip 7za.exe -out path/dirName -mapping your-project/path/mapping.txt
原理:经过算法扣掉了图片中一些相近的像素,达到下降质量的没得 只能下降File的大小,不能下降内存中的bitmap,bitmap是按照width*high来计算的的 。 只能将图片压缩后保存到本地,或者上传服务器。 Bitmap.compress(CompressFormat format, int quality, OutputStream stream)
减小单位尺寸上的像素值,真正意义上下降像素。 缓存缩略图,头像等。 int rotatio = 4;// 压缩倍数 Rect rect = new Rect(0,0,bitmap.getWidth() / rotatio,bitmap.getHeight()/rotatio); Canvas canvas = new Canvas(); canvas.save(); canvas.drawBitmap(bitmap,null,rect,null); canvas.restore();
insampleSize = 2
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png",options); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; options.inSampleSize = 8;
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png"); Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);
将SKIA引擎增长哈夫曼算法 利用libjpeg.so实现 --》微信apk也有了libwechatjpeg.so库 具体JNI实现
/*
* Copyright 2014 http://Bither.net
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bitherlibjni.h"
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>
//统一编译方式
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h" /* Common decls for cjpeg/djpeg applications */
#include "jpeg/jversion.h" /* for version message */
#include "jpeg/android/config.h"
}
#define LOG_TAG "jni"
#define LOGW(...) __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define true 1
#define false 0
typedef uint8_t BYTE;
char *error;
struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr * my_error_ptr;
METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
error=(char*)myerr->pub.jpeg_message_table[myerr->pub.msg_code];
LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
// LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
// LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
// LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
longjmp(myerr->setjmp_buffer, 1);
}
int generateJPEG(BYTE* data, int w, int h, int quality,
const char* outfilename, jboolean optimize) {
//jpeg的结构体,保存的好比宽、高、位深、图片格式等信息,至关于java的类
struct jpeg_compress_struct jcs;
//当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。
struct my_error_mgr jem;
jcs.err = jpeg_std_error(&jem.pub);
jem.pub.error_exit = my_error_exit;
if (setjmp(jem.setjmp_buffer)) {
return 0;
}
//初始化jsc结构体
jpeg_create_compress(&jcs);
//打开输出文件 wb:可写byte
FILE* f = fopen(outfilename, "wb");
if (f == NULL) {
return 0;
}
//设置结构体的文件路径
jpeg_stdio_dest(&jcs, f);
jcs.image_width = w;//设置宽高
jcs.image_height = h;
// if (optimize) {
// LOGI("optimize==ture");
// } else {
// LOGI("optimize==false");
// }
//看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */
jcs.arith_code = false;
int nComponent = 3;
/* 颜色的组成 rgb,三个 # of color components in input image */
jcs.input_components = nComponent;
//设置结构体的颜色空间为rgb
jcs.in_color_space = JCS_RGB;
// if (nComponent == 1)
// jcs.in_color_space = JCS_GRAYSCALE;
// else
// jcs.in_color_space = JCS_RGB;
//所有设置默认参数/* Default parameter setup for compression */
jpeg_set_defaults(&jcs);
//是否采用哈弗曼表数据计算 品质相差5-10倍
jcs.optimize_coding = optimize;
//设置质量
jpeg_set_quality(&jcs, quality, true);
//开始压缩,(是否写入所有像素)
jpeg_start_compress(&jcs, TRUE);
JSAMPROW row_pointer[1];
int row_stride;
//一行的rgb数量
row_stride = jcs.image_width * nComponent;
//一行一行遍历
while (jcs.next_scanline < jcs.image_height) {
//获得一行的首地址
row_pointer[0] = &data[jcs.next_scanline * row_stride];
//此方法会将jcs.next_scanline加1
jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:写入的行数
}
jpeg_finish_compress(&jcs);//结束
jpeg_destroy_compress(&jcs);//销毁 回收内存
fclose(f);//关闭文件
return 1;
}
/**
* byte数组转C的字符串
*/
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
char* rtn = NULL;
jsize alen = env->GetArrayLength( barr);
jbyte* ba = env->GetByteArrayElements( barr, 0);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements( barr, ba, 0);
return rtn;
}
jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
jclass thiz, jobject bitmapcolor, int w, int h, int quality,
jbyteArray fileNameStr, jboolean optimize) {
BYTE *pixelscolor;
//1.将bitmap里面的全部像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面
//处理bitmap图形信息方法1 锁定画布
AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);
//2.解析每个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面
BYTE *data;
BYTE r,g,b;
data = (BYTE*)malloc(w*h*3);//每个像素都有三个信息RGB
BYTE *tmpdata;
tmpdata = data;//临时保存data的首地址
int i=0,j=0;
int color;
for (i = 0; i < h; ++i) {
for (j = 0; j < w; ++j) {
//解决掉alpha
//获取二维数组的每个像素信息(四个部分a/r/g/b)的首地址
color = *((int *)pixelscolor);//经过地址取值
//0~255:
// a = ((color & 0xFF000000) >> 24);
r = ((color & 0x00FF0000) >> 16);
g = ((color & 0x0000FF00) >> 8);
b = ((color & 0x000000FF));
//改值!!!----保存到data数据里面
*data = b;
*(data+1) = g;
*(data+2) = r;
data = data + 3;
//一个像素包括argb四个值,每+4就是取下一个像素点
pixelscolor += 4;
}
}
//处理bitmap图形信息方法2 解锁
AndroidBitmap_unlockPixels(env,bitmapcolor);
char* fileName = jstrinTostring(env,fileNameStr);
//调用libjpeg核心方法实现压缩
int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
if(resultCode ==0){
jstring result = env->NewStringUTF("-1");
return result;
}
return env->NewStringUTF("1");
}
复制代码
FFmpeg中有双立方采样压缩跟三线性采样压缩,可是未引用FFmpeg的android项目没法使用,故采用上面这种方式。
1)冷启动:当直接从桌面上直接启动,同时后台没有该进程的缓存,这个时候系统就须要
从新建立一个新的进程而且分配各类资源。
2)热启动:该app后台有该进程的缓存,这时候启动的进程就属于热启动。
热启动不须要从新分配进程,也不会Application了,直接走的就是app的入口Activity,这样就速度快不少
复制代码
使用命令行来启动app,同时进行时间测量。单位:毫秒
adb shell am start -W [PackageName]/[PackageName.MainActivity]
adb shell am start -W project.study.koller.mystudyproject.app/project.study.koller.mystudyproject.app.MainActivity
ThisTime: 165 指当前指定的MainActivity的启动时间
TotalTime: 165 整个应用的启动时间,Application+Activity的使用的时间。
WaitTime: 175 包括系统的影响时间---比较上面大。
复制代码
Application从构造方法开始--->attachBaseContext()--->onCreate()
Activity构造方法--->onCreate()--->设置显示界面布局,设置主题、背景等等属性
--->onStart()--->onResume()--->显示里面的view(测量、布局、绘制,显示到界面上)
时间花在哪里了?
复制代码
1)、不要在Application的构造方法、attachBaseContext()、onCreate()里面进行初始化耗时操做。
2)、MainActivity,因为用户只关心最后的显示的这一帧,对咱们的布局的层次要求要减小,自定义控件的话测量、布局、绘制的时间。
不要在onCreate、onStart、onResume当中作耗时操做。
3)、对于SharedPreference的初始化。
由于他初始化的时候是须要将数据所有读取出来放到内存当中。
优化1:能够尽量减小sp文件数量(IO须要时间);2.像这样的初始化最好放到线程里面;3.大的数据缓存到数据库里面。
复制代码
app启动的耗时主要是在:Application初始化 + MainActivity的界面加载绘制时间。
因为MainActivity的业务和布局复杂度很是高,甚至该界面必需要有一些初始化的数据才能显示。 那么这个时候MainActivity就可能半天都出不来,这就给用户感受app太卡了。
咱们要作的就是给用户赶忙利落的体验。点击app就立马弹出咱们的界面。 因而乎想到使用SplashActivity--很是简单的一个欢迎页面上面都不干就只显示一个图片。
可是SplashActivity启动以后,仍是须要跳到MainActivity。MainActivity仍是须要从头开始加载布局和数据。 想到SplashActivity里面能够去作一些MainActivity的数据的预加载。而后须要经过意图传到MainActivity。
可不能够再作一些更好的优化呢? 耗时的问题:Application+Activity的启动及资源加载时间;预加载的数据花的时间。
若是咱们能让这两个时间重叠在一个时间段内并发地作这两个事情就省时间了。
将SplashActivity和MainActivity合为一个。
一进来仍是现实的MainActivity,SplashActivity能够变成一个SplashFragment,而后放一个FrameLayout做为根布局直接现实SplashFragment界面。
SplashFragment里面很是之简单,就是现实一个图片,启动很是快。
当SplashFragment显示完毕后再将它remove。同时在splash的2S的友好时间内进行网络数据缓存。
这个时候咱们才看到MainActivity,就没必要再去等待网络数据返回了。
问题:SplashView和ContentView加载放到一块儿来作了 ,这可能会影响应用的启动时间。
解决:可使用ViewStub延迟加载MainActivity当中的View来达到减轻这个影响。
复制代码
viewStub的设计就是为了防止MainActivity的启动加载资源太耗时了。延迟进行加载,不影响启动,用户友好。 可是viewStub加载也须要时间。等到主界面出来之后。 viewStub.inflate(xxxx);
第一时间想到的就是在onCreate里面调用handler.postDelayed()方法。
问题:这个延迟时间如何控制?
不一样的机器启动速度不同。这个时间如何控制?
假设,须要在splash作一个动画--2S
须要达到的效果:应用已经启动并加载完成,界面已经显示出来了,而后咱们再去作其余的事情。
复制代码
若是咱们这样:
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mProgressBar.setVisibility(View.GONE);
iv.setVisibility(View.VISIBLE);
}
}, 2500);
复制代码
是无法作到等应用已经启动并加载完成,界面已经显示出来了,而后咱们再去作其余的事情。2500ms并不能适配全部手机(CPU,内存大小,都不同)
问题:何时应用已经启动并加载完成,界面已经显示出来了。 onResume执行完了以后才显示完毕。 利用getWindow().getDecorView().post(Runable)便可
public class MainActivity extends FragmentActivity {
private Handler mHandler = new Handler();
private SplashFragment splashFragment;
private ViewStub viewStub;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
splashFragment = new SplashFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame, splashFragment);
transaction.commit();
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// mProgressBar.setVisibility(View.GONE);
// iv.setVisibility(View.VISIBLE);
// }
// }, 2500);
viewStub = (ViewStub)findViewById(R.id.content_viewstub);
//1.判断当窗体加载完毕的时候,立马再加载真正的布局进来
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
// 开启延迟加载
mHandler.post(new Runnable() {
@Override
public void run() {
//将viewstub加载进来
viewStub.inflate();
}
} );
}
});
//2.判断当窗体加载完毕的时候执行,延迟一段时间作动画。
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
// 开启延迟加载,也能够不用延迟能够立马执行(我这里延迟是为了实现fragment里面的动画效果的耗时)
mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment) ,2000);
// mHandler.post(new DelayRunnable());
}
});
//3.同时进行异步加载数据
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
}
static class DelayRunnable implements Runnable{
private WeakReference<Context> contextRef;
private WeakReference<SplashFragment> fragmentRef;
public DelayRunnable(Context context, SplashFragment f) {
contextRef = new WeakReference<Context>(context);
fragmentRef = new WeakReference<SplashFragment>(f);
}
@Override
public void run() {
// 移除fragment
if(contextRef!=null){
SplashFragment splashFragment = fragmentRef.get();
if(splashFragment==null){
return;
}
FragmentActivity activity = (FragmentActivity) contextRef.get();
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
transaction.remove(splashFragment);
transaction.commit();
}
}
}
}
复制代码
service:是一个后台服务,专门用来处理常驻后台的工做的组件。
即时通信:service来作常驻后台的心跳传输。 1.良民:核心服务尽量地轻!!! 不少人喜欢把全部的后台操做都集中在一个service里面。 为核心服务专门作一个进程,跟其余的全部后台操做隔离。 树大招风,核心服务千万要轻。
进程的重要性优先级:(越日后的就越容易被系统杀死) 1.前台进程;Foreground process 1)用户正在交互的Activity(onResume()) 2)当某个Service绑定正在交互的Activity。 3)被主动调用为前台Service(startForeground()) 4)组件正在执行生命周期的回调(onCreate()/onStart()/onDestroy()) 5)BroadcastReceiver 正在执行onReceive();
2.可见进程;Visible process 1)咱们的Activity处在onPause()(没有进入onStop()) 2)绑定到前台Activity的Service。
3.服务进程;Service process 简单的startService()启动。 4.后台进程;Background process 对用户没有直接影响的进程----Activity出于onStop()的时候。 android:process=":xxx" 5.空进程; Empty process 不含有任何的活动的组件。(android设计的,为了第二次启动更快,采起的一个权衡)
故事:小米撕逼。 背景:当手机锁屏的时候什么都干死了,为了省电。 锁屏界面在上面盖住了。 监听锁屏广播,锁了---启动这个Activity。 监听锁屏的, 开启---结束掉这个Activity。 要监听锁屏的广播---动态注册。 ScreenListener.begin(new xxxListener onScreenOff() );
被系统没法杀死的进程。
一个进程被杀死,另一个进程又被他启动。相互监听启动。
A<--->B
杀进程是一个一个杀的。本质是和杀进程时间赛跑。
复制代码
把任务加到系统调度队列中,当到达任务窗口期的时候就会执行,咱们能够在这个任务里面启动咱们的进程。 这样能够作到将近杀不死的进程。
AccountManager
服务器对象Object----流--->客户端Object对象
序列化: Serializable/Parcelable
时间:1ms * 10 * 50 * 20 = 10000ms 性能:内存的浪费和CPU计算时间的占用。
json/xml json序列化的工具GSON/fastjson
FlatBuffer:基于二进制的文件。 json:基于字符串的
VirtualAPK加载逻辑 1 宿主APPlication,新建PluginManager的Instance 2 PluginManager构造函数中Hook了Instrumention,Handler,AMS,新建ComponentHanlder 3 MainActiviy 加摘APK,PluginManager.LoadPlugin(apk) 4 建立LoadedPlugin 加载APK,缓存四大组件及Instrumention 5 尝试启动插件apk的application. 利用Instrumention.newApplication(DexClassloader,applicatoinClassName,activity的context); 6 给application注册activitylifecycle的回调监听 7 点击启动按钮,拦截execStartActivity方法,将目标Acitivity(即插件Acitivity)替换为占位Activity,即替换Intent. 8 拦截newActivity方法,先尝试用当前的classLoader加载参数class,若是抛出ClassNotFoundException即为插件Activity(由于占位Activity并无实体,仅在Manifest中注册),还原目标Intent,调用newActivity启动,并利用反射获取资源文件夹,将其赋值给插件Activity。
9 插件化出现android.view.inflateException是由于资源没有被加载进来,因此报错,资源找不到因此报错。用纯代码方式布局,无问题.
10 在小米9上(android9.0 MiUi10.2) VitrualAPK启动demo也有问题。
1 将bug的类修改完毕 2 利用dx工具(androidSdk自带)将其打包为dex文件 3 将dex文件下载到手机 4 利用DexClassLoader跟PathClassLoader的共同父类BaseDexLoader里面的DexPathList的dexElements数组,将其插入到数组最前面便可。
class BaseDexClassLoader{
DexPathList pathList;
}
class DexPathList{
Element[] dexElements;
}
复制代码
1.找到MyTestClass.class project_name\app\build\intermediates\bin\MyTestClass.class 2.配置dx.bat的环境变量 Android\sdk\build-tools\23.0.3\dx.bat 3.命令 dx --dex --output=D:\Users\adminstor\Desktop\dex\classes2.dex D:\Users\adminstor\Desktop\dex 命令解释: --output=D:\Users\adminstor\Desktop\dex\classes2.dex 指定输出路径 D:\Users\adminstor\Desktop\dex 最后指定去打包哪一个目录下面的class字节文件(注意要包括全路径的文件夹,也能够有多个class)
而后利用反射将其加载进app,等到下次启动便可。
public class FixDexUtils {
private static HashSet<File> loadedDex = new HashSet<File>();
static{
loadedDex.clear();
}
public static void loadFixedDex(Context context){
if(context == null){
return ;
}
//遍历全部的修复的dex
File fileDir = context.getDir(MyConstants.DEX_DIR,Context.MODE_PRIVATE);
File[] listFiles = fileDir.listFiles();
for(File file:listFiles){
if(file.getName().startsWith("classes")&&file.getName().endsWith(".dex")){
loadedDex.add(file);//存入集合
}
}
//dex合并以前的dex
doDexInject(context,fileDir,loadedDex);
}
private static void setField(Object obj,Class<?> cl, String field, Object value) throws Exception {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
localField.set(obj,value);
}
private static void doDexInject(final Context appContext, File filesDir,HashSet<File> loadedDex) {
String optimizeDir = filesDir.getAbsolutePath()+File.separator+"opt_dex";
File fopt = new File(optimizeDir);
if(!fopt.exists()){
fopt.mkdirs();
}
//1.加载应用程序的dex
try {
PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
for (File dex : loadedDex) {
//2.加载指定的修复的dex文件。
DexClassLoader classLoader = new DexClassLoader(
dex.getAbsolutePath(),//String dexPath,
fopt.getAbsolutePath(),//String optimizedDirectory,
null,//String libraryPath,
pathLoader//ClassLoader parent
);
//3.合并
Object dexObj = getPathList(classLoader);
Object pathObj = getPathList(pathLoader);
Object mDexElementsList = getDexElements(dexObj);
Object pathDexElementsList = getDexElements(pathObj);
//合并完成
Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
//重写给PathList里面的lement[] dexElements;赋值
Object pathList = getPathList(pathLoader);
setField(pathList,pathList.getClass(),"dexElements",dexElements);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static Object getField(Object obj, Class<?> cl, String field)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
private static Object getPathList(Object baseDexClassLoader) throws Exception {
return getField(baseDexClassLoader,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList");
}
private static Object getDexElements(Object obj) throws Exception {
return getField(obj,obj.getClass(),"dexElements");
}
/**
* 两个数组合并
* @param arrayLhs
* @param arrayRhs
* @return
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class<?> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
}
复制代码