第一章,
Android 5.6.7新特性
1、RecycleView的自定义分割线
public class DividerItemDecoration extends RecycleView.ItemDecoration {
}
2、自定义点击事件
3、CardView
4、运行时权限问题
Normal Permissions 不涉及用户隐私,在androidMainfest.xml中申明就好
,无需每次使用的时候都检查权限,且用户不可以取消权限;
Dangerous Permission 涉及用户隐私,需要用户授权
checkSelfPermission
onRequestPermissionResult
PermissionsDispatcher
第4章 多线程编程
实现线程的方式
1、thread\runnable
2、再写一遍callable接口的写法
public static class MyCallable implements Callable {
public String call() {
....
}
}
在main方法中
ExecutorService service = Executors.newSingleThreadPool();
Future future = service.submit(new MyCallable()) ;
try {
futurer.get();
}catch() {
}
注意 类变量放在方法区(静态变量,类的共有信息),成员变量放在堆内存上, 局部变量放在栈内存上
3、Volatile 和synchronized关键字的区别
Volatile是可以让多个线程感知这个值的变化,这个值放在主存当中
Synchronized则是锁主这个变量,只有拥有锁的这个线程可以访问,其他线程将被阻塞
Volatile不能保证原子性,不可以用作多线程的自增;
4、重入锁和条件对象
重入锁和不可以重入锁的区别
不可以重入锁是指当前线程在某个方法中已经获取到锁的时候,没有释放前,在其他方法中是没办法再次获取到锁的
重入锁指的是对某个资源可以进行重复加锁
public void count() {
lock.lock();
doadd();
lock.unlock();
}
public void doadd() {
lock.lock();
//dosomething;
lock.unlock();
}
在do add方法中就没办法再次获取到锁
条件对象是指获取到锁后,需要满足的条件,比如支付转账,获取到锁后,因为当前用户余额不满足转账的金额(条件对象),则会阻塞其他线程
所以我们会new Reentantlock().newCondition()l;获取到条件对象,当当前用户余额不满足转账的金额(条件对象)的时候,condition.await();阻塞当前线程。并且放弃锁;当执行完转账操作的时候,则condition.signalAll();唤醒其他线程;
5、阻塞队列
LinkedBlockQueue 先进先出,生产者往队列中放入一个数据后,边返回
6、线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
//1. 第一个参数是核心的线程数目 , CORE_POOL_SIZE,
//2.第二个参数 是线程池的最大数目, MAX_POOL_SIZE,
//3.第三个参数是非核心线程等待新任务的时长 KEEP_LIVE;
//4.第4个参数是阻塞队列。blockQueue
//5.第5个参数是线程工厂 ThreadFactory
);
线程池的 处理流程:
7、AsyncTask源码分析:
public AsyncTask(@Nullable Looper callbackLooper) {
//代表的是否是主线程的looper
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
//
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
上面是构造函数,通常我们在继承AsyncTask的时候,在主线程使用AsyncTask,确实也没必须要在子线程使用啊
new MyAsyncTask(this)
可以看到
WorkerRunnable 实现了callable接口
mFuture实现了futureTask 。mWork作为参数传递给了mFuture,当执行mFuture的run方法的时候会调用到mWorker的call方法(那么什么时候会调用到mFuture的run方法呢??后面会说),然后执行
result = doInBackground(mParams);//此处的doInBackGrong方法是抽象方法由用户实现;
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
postResult传递结果给InternalHandler,执行完成后,执行finsh方法,否则执行onProgressUpdate方法(用户可以重载)
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
onPostExecute方法用户可重载
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
到此,大家看到了熟悉的
doInBackground
onPostExecute
onProgressUpdate
但是
onPreExecute方法呢???AsyncTask的execute方法呢???
//TODO 必须要在主线程执行,Params就是传入的参数
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
如下,当用户调用execute方法的时候,会调用executeOnExecutor方法
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
//执行前要做的事情
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
到这里我们看到了OnPreExecute方法,这里的exec就是传入的sDefaultExecutors;
r.run执行了我们刚才遗留的问题,会调用到mWorker中的call投递结果
//这里的sDefaultExecutors = new SerialExecutor串行的线程池
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
//这里的r就是mFuture
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
//此处执行mFuture run最终会执行mWorker的call方法投递结果
r.run();
} finally {
//执行完了使用线程池调度下一个
scheduleNext();
}
}
});
//当前没有任务
if (mActive == null) {
scheduleNext();
}
}
//如果执行完或者没有任务就会调用线程池执行下一个
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
小小的总结一下AsyncTask的分析吧: 创建了一个串行执行的线程池,当用户在主线程执行new AsyncTask.execute(params)方法的时候,会从当前的线程池中取出一个线程去执行,先可以看到我们的onPreExecute方法,然后线程池中的线程会调用mWorker.call方法,这个方法里封装了用户要复写的ondoingbackground方法,并且最终就结果投递出去,在finsh方法中我们可以看到onPostExecute方法,将状态机置为finish,到此结束
第5章 Retrofit源码分析
5.1 Retrofit 使用举例
1、访问的例子
/**
* 普通GET请求
*/
private void getIpInformation() {
String url = "http://ip.taobao.com/service/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
IpService ipService = retrofit.create(IpService.class);
Call<IpModel> call = ipService.getIpMsg();
call.enqueue(new Callback<IpModel>() {
@Override
public void onResponse(Call<IpModel> call, Response<IpModel> response) {
Log.e(TAG, "### nResponse: ");
String country = response.body().getData().getCountry();
Log.i(TAG, "### country" + country);
Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<IpModel> call, Throwable t) {
Log.e(TAG, "### onFailure: ");
}
});
}
特性要注意一个事情,我调试了好久
public interface IpService {
@Headers({
"Accept-Encoding: application/json",
"User-Agent: xiaoyubo"
})
@GET("getIpInfo.php?ip=59.108.54.37")
Call<IpModel> getIpMsg();
}
如果使用模拟器进行调测的话请加上上述的请求头!!!!!!!!!
否则请求不到数据,注意当使用其他虚拟机请求的时候,使用fillder抓包看下有没有这个user-Agent的字段,因为有的服务器会拦截这个请求;
再来看下工建造者模式的写法: 以后就不要再写很多的构造函数啦
package com.whut.android_andvaced_light.retrofit;
public class NutritionFacts {
private final int servingSize; //TODO 必须的
private final int servings; //TODO 必须的
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
//一个内部类
//以后维护的时候,添加新参数的时候也很方便
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
//构造函数需要传builder
//可以使用单个builder构建多个对象
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
//具有安全性和可读性
//build的时候才会验证参数是否正确
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
}
第8章 函数响应式编程
异步操作
说道异步操作我们通常是想到 handler + asyncTask的框架,然而随着代码逻辑的复杂程度和请求数量越来越多,不像RXJAVA可以保持很清晰的逻辑,如同流水线一样,RXJAVA一步步将所需要的内容进行加工,最终形成你想要的成品,然后发射给subscribe进行处理。
想想我们的线程切换。通常在先要开启子线程,然后异步请求完成后,通过handler切换到主线程然后再刷新UI
Retrofit retrofit1 = new Retrofit.Builder().baseUrl("/").addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build();
final GetRequest_Interface getRequest_interface = retrofit1.create(GetRequest_Interface.class);
getRequest_interface.getCall().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Translation_1>() {
@Override
public void accept(Translation_1 translation_1) throws Exception {
}
});
在MainActivity需要中需要销毁activity的时候就要取消请求
在onStop中
@Override
protected void onStop() {
LogUtil.d(TAG, "====>onStop");
super.onStop();
if ((null != mDispose) && (!mDispose.isDisposed())) {
mDispose.dispose();
}
}
RxJava的源码解析
1、RxJava的订阅流程
第一步,首先要创建一个被观察者Observable,有很多方法。just\fromArray\Create
@Test
public void TestRxJava() {
// 创建一个消费者,也叫观察者,用来消费注册的事件
Consumer<String> consumer = o -> {
if (!o.isEmpty()) {
System.out.println(o);
}
};
// 被观察者注册一个消费的事件
Observable.just("1").subscribe(consumer);
}
Consumer<String> successObserver = new Consumer<String>() {
@Override
public void accept(String o) throws Exception {
if (!o.isEmpty()) {
System.out.println(o);
}
}
};
Consumer<Throwable> failObserver = new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
}
};
subscribe = Observable.just("1").subscribe(successObserver, failObserver);
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> observableEmitter) throws Exception {
observableEmitter.onNext("1");
}
}).doOnNext(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
System.out.println(s + "do on next");
}
}).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable disposable) {
}
@Override
public void onNext(String s) {
System.out.println(s);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
}
});
第二步,创建一个观察者,也就是Consumer
第三步,观察者订阅事件
2、RxJava的线程切换流程
在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe()
,就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler
(调度器)。
Observable.just(1, 2, 3, 4)
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer number) {
Log.d(tag, "number:" + number);
}
});
3、RxJava的变换
flatMap & Map
4、RxJava+Retrofit
getUser(userId)
.doOnNext(new Action1<User>() {
@Override
public void call(User user) {
processUser(user);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onNext(User user) {
userView.setUser(user);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable error) {
// Error handling
...
}
});
5、RxJava+RxBinding
6、RxJava+RxBus
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
过了个年哈,,继续读书笔记Android进阶解密
第一章 Android 系统架构
1、app 应用层(launcher、dialer、carmera、calendar);
2、framework层(为应用包含系统应用提供api接口 包含view System\Content Providers\Manger(Window Manager、PackageManager、acitivityManager))
3、系统运行层(Native 层 (C++程序库 + AndroidRuntime(coreLibrary)))
4、HAL层,硬件抽象层(wifi bluetooth audio)
5、linux kernel
第二章 Android系统启动
/system/core/init init 进程是Andoird系统启动的第一个进程
按下电源后,首先系统会加载引导程序bootloader,引导程序启动linux 内核,linixu内核加载完成后,在系统文件中寻找init.rc 文件,启动init进程
init进程又会启动zygote进程;
1、init 进程的入口函数:
init.rc文件会创建init进程,在init进程的main函数中会去解析init.rc的service
这里会启动zygote进程,service是Android init language语句、init进程拉起一个名字为zygote的进程,位于/system/bin/app_process64位置,
1 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
2 class main
3 priority -20
4 user root
5 group root readproc reserved_disk
6 socket zygote stream 660 root system
7 onrestart write /sys/android_power/request_state wake
8 onrestart write /sys/power/state on
9 onrestart restart audioserver
10 onrestart restart cameraserver
11 onrestart restart media
12 onrestart restart netd
13 onrestart restart wificond
14 writepid /dev/cpuset/foreground/tasks
2、init进程是如何启动zygote进程的 app_main.cpp函数中的main函数:
349 if (zygote) {
350 runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
351 } else if (className) {
352 runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
353 } else {
354 fprintf(stderr, "Error: no class name or --zygote supplied.\n");
355 app_usage();
356 LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
357 }
此时启动zygote进程;
3、属性服务
init进程的启动总结:
1、挂在启动所需要的文件目录;
2、初始化和启动属性服务;
3、解析init.rx配置文件并且启动zygote进程;
2、Zygote进程
zygote进程创建了DVM ART 应用进程程序、运行系统的关键服务systemServer;
在init进程中通过AndoridRuntime的start函数 做了如下几个事情;
a、 startVM 开启java虚拟机
b、startJniRegist 注册jni函数
c、找到zygoteinit.java文件的main方法;在1138处执行;由此调用到java层;通过jni调用到了zygoteinit的java层的main函数;
1132 char* slashClassName = toSlashClassName(className != NULL ? className : "");
1133 jclass startClass = env->FindClass(slashClassName);
1134 if (startClass == NULL) {
1135 ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
1136 /* keep going */
1137 } else {
1138 jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
1139 "([Ljava/lang/String;)V");
1140 if (startMeth == NULL) {
1141 ALOGE("JavaVM unable to find main() in '%s'\n", className);
1142 /* keep going */
1143 } else {
1144 env->CallStaticVoidMethod(startClass, startMeth, strArray);
1145
zygote进程总结:
创建appRuntime,调用start方法创建zygote进程;
启动jvm 并且注册jni方法;
启动systemServer;
通过jni调用zygoteInit的main函数进入zygote的java层;
等待AMS请求创建新的应用进程;
3、SystemServer进程
用来创建引导服务、核心服务 、其他服务;
如下代码片段;在SystemServer.java文件中
Installer installer = mSystemServiceManager.startService(Installer.class);
549 traceEnd();
550
551 // In some cases after launching an app we need to access device identifiers,
552 // therefore register the device identifier policy before the activity manager.
553 traceBeginAndSlog("DeviceIdentifiersPolicyService");
554 mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
555 traceEnd();
556
557 // Activity manager runs the show.
558 traceBeginAndSlog("StartActivityManager");
559 mActivityManagerService = mSystemServiceManager.startService(
560 ActivityManagerService.Lifecycle.class).getService();
561 mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
562 mActivityManagerService.setInstaller(installer);
563 traceEnd();
564
565 // Power manager needs to be started early because other services need it.
566 // Native daemons may be watching for it to be registered so it must be ready
567 // to handle incoming binder calls immediately (including being able to verify
568 // the permissions for those calls).
569 traceBeginAndSlog("StartPowerManager");
570 mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
571 traceEnd();
SystemServer进程总结;
启动binder线程池可以与其他进程通信;
启动系统服务;
4、launcher的启动
由AMS启动;
AMS由SystemServer启动,SystemServer由Zygote进程启动,zygote由Init进程启动,init由init_rc脚本启动;
第17章 内存优化
内存泄露是指本应该在生命周期结束的时候,被GC回收,但是此时它还在被引用,所以致内存泄漏。随着泄漏的累积,app将消耗完内存。
1、常见的内存泄露的情况:
1、在单例中持有Context对象
如下代码片段中,单利模式是他的生命周期和我们的应用是一样长的,当持有Activity的强引用的时候,Acitivy的context不会被回收,造成内存泄露;
public class Singleton {
private static Singleton singleton;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public Singleton getInstance(Context context) {
if (singleton == null) {
synchronized (Singleton.class){
if (singleton == null) {
singleton = new Singleton(context);
}
}
}
return singleton;
}
}
正确的做法应该是持有ApplicationContext;
再比如如下代码,当子线程持有主线程的引用的时候,子线程未结束前,主线程的activity都不会被回收,但是ondesoty会被执行;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG, "==== onCreate: ====");
new Thread(new Runnable() {
private Context context;
@Override
public void run() {
Log.e(TAG, "====run: ===");
SystemClock.sleep(5 * 1000);
context = MainActivity.this;
Log.e(TAG, "run: end");
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "======== onDestroy: ========");
}
2、handler引起的内存泄露
public class HandlerActivity extends Activity{
//可能引起内存泄漏的方法
private final Handler mHandler = new Handler(){
@Override
public void handlerMessage(Message msg){
//...
}
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//延迟5分钟发送消息
mHandler.postDelayed(new Runnable(){
@Override
public void run(){
...
}
},1000*60*5)
}
}
非静态的匿名内部类,持有外部类的引用,当Acitivty消失的时候,此时的MessageQueue里的消息还没有被取走,导致handler仍然被引用,且handler是非静态的匿名内部类,持有外部类的引用,故此时Aciticy消失的时候不会被销毁,导致内存泄露;
3、匿名内部类runnable2此时持有了Acitivy的引用,造成runnable2未结束时,activity被销毁但是不会回收;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inner_bad);
Runnable runnable1 = new MyRunnable();
Runnable runnable2 = new Runnable() {
@Override
public void run() {
}
};
}
private static class MyRunnable implements Runnable{
@Override
public void run() {
}
}
}
4、集合中的对象要被清理
5、bitmap对象要被回收;
2、内存泄露的相关分析工具
MemtoryMonitor 可以查看内存的使用情况,内存抖动(频繁的分配内存&回收内存,导致绘制线程受阻),是否有频繁的gc.
使用leakCany
1、引入依赖项
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
public class MyApplication extends Application {
private static final String TAG = MyApplication.class.getSimpleName();
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "===== MyApplication onCreate: =====");
refWatcher = setupLeakCanary();
}
private RefWatcher setupLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWatch(Context context) {
MyApplication myApplication = (MyApplication) context.getApplicationContext();
return myApplication.refWatcher;
}
}
第16 章绘制优化
1、绘制原理
View的绘制包含 measure,layout,draw
60fps(1秒钟60帧)让人感受不到卡顿,当低于60fps之后则明显感觉卡顿,
影响卡顿的原因:
布局layout复杂,无法再10ms内完成绘制;
UI线程做了好事操作
GC回收时间过长或者频繁GC
工具:
Systrace:
不咋好用点击DDMS中的start Method profilng,看到耗时的方法
2、点击hierarchy View查看视图的层级
1、嵌套层级需要合理的减少
2、使用include标签来进行布局的复用
3、用 merge标签来去除多余层级
比入根布局是linearlayout ,linearlayout中有recycleview 每个item有嵌套linearlayout,则 可以使用merge标签,减少嵌套层级
有一个问题就是merge标签最好是用于替代布局方向一致的布局
4、使用viewstub来加高加载速度
viewstub用来替代布局中不需要显示出来的view,在对于viewstub使用inflate房钱前,他是不占布局看下控件和资源的
viewstub,inlate ; viewstub,setvisiabilty(Visiavble) viewStub操作的是布局文件
避免过度绘制就是移除不需要的background