执行此操作的一个好方法是,在片段内定义一个回调接口,并要求宿主Activity实现它。
publicstaticclassFragmentAextendsListFragment{...//ContainerActivitymustimplementthisinterfacepublicinterfaceOnArticleSelectedListener{publicvoidonArticleSelected(UriarticleUri);}...}publicstaticclassFragmentAextendsListFragment{OnArticleSelectedListenermListener;...@OverridepublicvoidonAttach(Activityactivity){super.onAttach(activity);try{mListener=(OnArticleSelectedListener)activity;}catch(ClassCastExceptione){thrownewClassCastException(activity.toString());}}...}ServiceService分为两种工作状态,一种是启动状态,主要用于执行后台计算;另一种是绑定状态,主要用于其他组件和Service的交互。
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(receiver,filter);注册过程ContentProviderContentProvider管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。内容提供程序是连接一个进程中的数据与另一个进程中运行的代码的标准界面。
ContentProvider无法被用户感知,对于一个ContentProvider组件来说,它的内部需要实现增删该查这四种操作,它的内部维持着一份数据集合,这个数据集合既可以是数据库实现,也可以是其他任何类型,如List和Map,内部的insert、delete、update、query方法需要处理好线程同步,因为这几个方法是在Binder线程池中被调用的。
ContentProvider通过Binder向其他组件乃至其他应用提供数据。当ContentProvider所在的进程启动时,ContentProvider会同时启动并发布到AMS中,需要注意的是,这个时候ContentProvider的onCreate要先于Application的onCreate而执行。
//QueriestheuserdictionaryandreturnsresultsmCursor=getContentResolver().query(UserDictionary.Words.CONTENT_URI,//ThecontentURIofthewordstablemProjection,//ThecolumnstoreturnforeachrowmSelectionClause//SelectioncriteriamSelectionArgs,//SelectioncriteriamSortOrder);//ThesortorderforthereturnedrowspublicclassInstallerextendsContentProvider{@OverridepublicbooleanonCreate(){returntrue;}@Nullable@OverridepublicCursorquery(@NonNullUriuri,@NullableString[]projection,@NullableStringselection,@NullableString[]selectionArgs,@NullableStringsortOrder){returnnull;}@Nullable@OverridepublicStringgetType(@NonNullUriuri){returnnull;}@Nullable@OverridepublicUriinsert(@NonNullUriuri,@NullableContentValuesvalues){returnnull;}@Overridepublicintdelete(@NonNullUriuri,@NullableStringselection,@NullableString[]selectionArgs){return0;}@Overridepublicintupdate(@NonNullUriuri,@NullableContentValuesvalues,@NullableStringselection,@NullableString[]selectionArgs){return0;}}ContentProvider和sql在实现上有什么区别
ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联
View的整个绘制流程可以分为以下三个阶段:
MeasureSpec表示的是一个32位的整形值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize。MeasureSpec是View类的一个静态内部类,用来说明应该如何测量这个View
对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定
直接继承View的控件需要重写onMeasure方法并设置wrap_content时的自身大小,因为View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于父容器当前剩余的空间大小,就相当于使用match_parent。这解决方式如下:
getX/getY返回相对于当前View左上角的坐标,getRawX/getRawY返回相对于屏幕左上角的坐标
TouchSlop是系统所能识别出的被认为滑动的最小距离,不同设备值可能不相同,可通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取。
VelocityTracker可用于追踪手指在滑动中的速度:
view.setOnTouchListener(newView.OnTouchListener(){@OverridepublicbooleanonTouch(Viewv,MotionEventevent){VelocityTrackervelocityTracker=VelocityTracker.obtain();velocityTracker.addMovement(event);velocityTracker.computeCurrentVelocity(1000);intxVelocity=(int)velocityTracker.getXVelocity();intyVelocity=(int)velocityTracker.getYVelocity();velocityTracker.clear();velocityTracker.recycle();returnfalse;}});GestureDetectorGestureDetector辅助检测用户的单击、滑动、长按、双击等行为:
弹性滑动对象,用于实现View的弹性滑动,Scroller本身无法让View弹性滑动,需要和View的computeScroll方法配合使用。startScroll方法是无法让View滑动的,invalidate会导致View重绘,重回后会在draw方法中又会去调用computeScroll方法,computeScroll方法又会去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo方法实现滑动,接着又调用postInvalidate方法如此反复。
ScrollermScroller=newScroller(mContext);privatevoidsmoothScrollTo(intdestX){intscrollX=getScrollX();intdelta=destX-scrollX;//1000ms内滑向destX,效果就是慢慢滑动mScroller.startScroll(scrollX,0,delta,0,1000);invalidate();}@OverridepublicvoidcomputeScroll(){if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(),mScroller.getCurrY());postInvalidate();}}View的滑动publicvoidscrollTo(intx,inty){if(mScrollX!=x||mScrollY!=y){intoldX=mScrollX;intoldY=mScrollY;mScrollX=x;mScrollY=y;invalidateParentCaches();onScrollChanged(mScrollX,mScrollY,oldX,oldY);if(!awakenScrollBars()){postInvalidateOnAnimation();}}}publicvoidscrollBy(intx,inty){scrollTo(mScrollX+x,mScrollY+y);}mScrollX的值等于View的左边缘和View内容左边缘在水平方向的距离,mScrollY的值等于View上边缘和View内容上边缘在竖直方向的距离。scrollTo和scrollBy只能改变View内容的位置而不能改变View在布局中的位置。
ViewGroup.MarginLayoutParamsparams=(ViewGroup.MarginLayoutParams)view.getLayoutParams();params.width+=100;params.leftMargin+=100;view.requestLayout();//或者view.setLayoutParams(params);View的事件分发点击事件达到顶级View(一般是一个ViewGroup),会调用ViewGroup的dispatchTouchEvent方法,如果顶级ViewGroup拦截事件即onInterceptTouchEvent返回true,则事件由ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用。也就是说如果都提供的话,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了mOnClickListenser,则onClick会被调用。如果顶级ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。如此循环。
主要用于实现自定义布局,采用这种方式需要合适地处理ViewGroup的测量、布局两个过程,并同时处理子元素的测量和布局过程。
用于扩张某种已有的View的功能
用于扩张某种已有的ViewGroup的功能
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
当某个应用组件启动且该应用没有运行其他任何组件时,Android系统会使用单个执行线程为应用启动新的Linux进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。
1、前台进程
2、可见进程
3、服务进程
4、后台进程
5、空进程
如果注册的四大组件中的任意一个组件时用到了多进程,运行该组件时,都会创建一个新的Application对象。对于多进程重复创建Application这种情况,只需要在该类中对当前进程加以判断即可。
只要实现了Parcelable接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。
IPC即Inter-ProcessCommunication(进程间通信)。Android基于Linux,而Linux出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。
在Linux系统中,虚拟内存机制为每个进程分配了线性连续的内存空间,操作系统将这种虚拟内存空间映射到物理内存空间,每个进程有自己的虚拟内存空间,进而不能操作其他进程的内存空间,只有操作系统才有权限操作物理内存空间。进程隔离保证了每个进程的内存安全。
Binder是Android中的一个类,实现了IBinder接口。从IPC角度来说,Binder是Android中的一种扩进程通信方方式。从Android应用层来说,Binder是客户端和服务器端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象。
Binder相较于传统IPC来说更适合于Android系统,具体原因的包括如下三点:
示例:
RemoteService.aidl
packagecom.example.mystudyapplication3;interfaceIRemoteService{intgetUserId();}系统会自动生成IRemoteService.java:
AndroidInterfaceDefinitionLanguage
使用示例:
Window是一个抽象类,它的具体实现是PhoneWindow。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。Android中所有的视图都是通过Window来呈现,因此Window实际是View的直接管理者。
Window是一个抽象的概念,每一个Window对应着一个View和一个ViewRootImpl。Window实际是不存在的,它是以View的形式存在。对Window的访问必须通过WindowManager,WindowManager的实现类是WindowManagerImpl:
WindowManagerImpl.java
@OverridepublicvoidaddView(@NonNullViewview,@NonNullViewGroup.LayoutParamsparams){applyDefaultToken(params);mGlobal.addView(view,params,mContext.getDisplay(),mParentWindow);}@OverridepublicvoidupdateViewLayout(@NonNullViewview,@NonNullViewGroup.LayoutParamsparams){applyDefaultToken(params);mGlobal.updateViewLayout(view,params);}@OverridepublicvoidremoveView(Viewview){mGlobal.removeView(view,false);}WindowManagerImpl没有直接实现Window的三大操作,而是全部交给WindowManagerGlobal处理,WindowManagerGlobal以工厂的形式向外提供自己的实例:
WindowManagerGlobal.java
在Activity的创建过程中,最终会由ActivityThread的performLaunchActivity()来完成整个启动过程,该方法内部会通过类加载器创建Activity的实例对象,并调用attach方法关联一系列上下文环境变量。在Activity的attach方法里,系统会创建所属的Window对象并设置回调接口,然后在Activity的setContentView方法中将视图附属在Window上:
Activity.java
finalvoidattach(Contextcontext,ActivityThreadaThread,Instrumentationinstr,IBindertoken,intident,Applicationapplication,Intentintent,ActivityInfoinfo,CharSequencetitle,Activityparent,Stringid,NonConfigurationInstanceslastNonConfigurationInstances,Configurationconfig,Stringreferrer,IVoiceInteractorvoiceInteractor,Windowwindow,ActivityConfigCallbackactivityConfigCallback){attachBaseContext(context);mFragments.attachHost(null/*parent*/);mWindow=newPhoneWindow(this,window,activityConfigCallback);mWindow.setWindowControllerCallback(this);mWindow.setCallback(this);mWindow.setOnWindowDismissedCallback(this);mWindow.getLayoutInflater().setPrivateFactory(this);if(info.softInputMode!=WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED){mWindow.setSoftInputMode(info.softInputMode);}if(info.uiOptions!=0){mWindow.setUiOptions(info.uiOptions);}···}···publicvoidsetContentView(@LayoutResintlayoutResID){getWindow().setContentView(layoutResID);initWindowDecorActionBar();}PhoneWindow.java
voidmakeVisible(){if(!mWindowAdded){ViewManagerwm=getWindowManager();wm.addView(mDecor,getWindow().getAttributes());mWindowAdded=true;}mDecor.setVisibility(View.VISIBLE);}Dialog的Window创建过程Dialog的Window的创建过程和Activity类似,创建同样是通过PolicyManager的makeNewWindow方法完成的,创建后的对象实际就是PhoneWindow。当Dialog被关闭时,会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)。
Dialog.java
Dialog(@NonNullContextcontext,@StyleResintthemeResId,booleancreateContextThemeWrapper){···mWindowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);finalWindoww=newPhoneWindow(mContext);mWindow=w;w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setOnWindowSwipeDismissedCallback(()->{if(mCancelable){cancel();}});w.setWindowManager(mWindowManager,null,null);w.setGravity(Gravity.CENTER);mListenersHandler=newListenersHandler(this);}普通Dialog必须采用Activity的Context,采用Application的Context就会报错,是因为应用token所导致,应用token一般只有Activity拥有。系统Window比较特殊,不需要token。
Toast属于系统Window,由于其具有定时取消功能,所以系统采用了Handler。Toast的内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口。
Toast内部的视图由两种方式,一种是系统默认的样式,另一种是setView指定一个自定义View,它们都对应Toast的一个内部成员mNextView。
Toast.java
publicvoidshow(){if(mNextView==null){thrownewRuntimeException("setViewmusthavebeencalled");}INotificationManagerservice=getService();Stringpkg=mContext.getOpPackageName();TNtn=mTN;tn.mNextView=mNextView;try{service.enqueueToast(pkg,tn,mDuration);}catch(RemoteExceptione){//Empty}}···publicvoidcancel(){mTN.cancel();}NotificationManagerService.java
Bitmap中有两个内部枚举类:
通常我们优化Bitmap时,当需要做性能优化或者防止OOM,我们通常会使用Bitmap.Config.RGB_565这个配置,因为Bitmap.Config.ALPHA_8只有透明度,显示一般图片没有意义,Bitmap.Config.ARGB_4444显示图片不清楚,Bitmap.Config.ARGB_8888占用内存最多。
Matrixmatrix=newMatrix();//缩放matrix.postScale(0.8f,0.9f);//左旋,参数为正则向右旋matrix.postRotate(-45);//平移,在上一次修改的基础上进行再次修改set每次操作都是最新的会覆盖上次的操作matrix.postTranslate(100,80);//裁剪并执行以上操作Bitmapbitmap=Bitmap.createBitmap(source,0,0,source.getWidth(),source.getHeight(),matrix,true);虽然Matrix还可以调用postSkew方法进行倾斜操作,但是却不可以在此时创建Bitmap时使用。
AndroidP中WindowManager.LayoutParams新增了一个布局参数属性layoutInDisplayCutoutMode:
不同厂商的刘海屏适配方案不尽相同,需分别查阅各自的开发者文档。
Context本身是一个抽象类,是对一系列系统服务接口的封装,包括:内部资源、包、类加载、I/O操作、权限、主线程、IPC和组件启动等操作的管理。ContextImpl,Activity,Service,Application这些都是Context的直接或间接子类,关系如下:
ContextWrapper是代理Context的实现,简单地将其所有调用委托给另一个Context(mBase)。
Application、Activity、Service通过attach()调用父类ContextWrapper的attachBaseContext(),从而设置父类成员变量mBase为ContextImpl对象,ContextWrapper的核心工作都是交给mBase(ContextImpl)来完成,这样可以子类化Context以修改行为而无需更改原始Context。
SharedPreferences采用key-value(键值对)形式,主要用于轻量级的数据存储,尤其适合保存应用的配置参数,但不建议使用SharedPreferences来存储大规模的数据,可能会降低性能.
Activity.getPreferences(mode):以当前Activity的类名作为SP的文件名.即xxxActivity.xmlActivity.java
publicSharedPreferencesgetPreferences(intmode){returngetSharedPreferences(getLocalClassName(),mode);}getDefaultSharedPreferencesPreferenceManager.getDefaultSharedPreferences(Context):以包名加上_preferences作为文件名,以MODE_PRIVATE模式创建SP文件.即packgeName_preferences.xml.
publicstaticSharedPreferencesgetDefaultSharedPreferences(Contextcontext){returncontext.getSharedPreferences(getDefaultSharedPreferencesName(context),getDefaultSharedPreferencesMode());}getSharedPreferences直接调用Context.getSharedPreferences(name,mode),所有的方法最终都是调用到如下方法:
SharedPreferences与Editor只是两个接口.SharedPreferencesImpl和EditorImpl分别实现了对应接口。另外,ContextImpl记录着SharedPreferences的重要数据。
putxxx()操作把数据写入到EditorImpl.mModified;
apply()/commit()操作先调用commitToMemory(),将数据同步到SharedPreferencesImpl的mMap,并保存到MemoryCommitResult的mapToWriteToDisk,再调用enqueueDiskWrite(),写入到磁盘文件;先之前把原有数据保存到.bak为后缀的文件,用于在写磁盘的过程出现任何异常可恢复数据;
getxxx()操作从SharedPreferencesImpl.mMap读取数据.
Handler有两个主要用途:(1)安排Message和runnables在将来的某个时刻执行;(2)将要在不同于自己的线程上执行的操作排入队列。(在多个线程并发更新UI的同时保证线程安全。)
Android规定访问UI只能在主线程中进行,因为Android的UI控件不是线程安全的,多线程并发访问会导致UI控件处于不可预期的状态。为什么系统不对UI控件的访问加上锁机制?缺点有两个:加锁会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率。如果子线程访问UI,那么程序就会抛出异常。ViewRootImpl对UI操作做了验证,这个验证工作是由ViewRootImpl的checkThread方法完成:
ViewRootImpl.java
voidcheckThread(){if(mThread!=Thread.currentThread()){thrownewCalledFromWrongThreadException("Onlytheoriginalthreadthatcreatedaviewhierarchycantouchitsviews.");}}Handler创建的时候会采用当前线程的Looper来构造消息循环系统,需要注意的是,线程默认是没有Looper的,直接使用Handler会报错,如果需要使用Handler就必须为线程创建Looper,因为默认的UI主线程,也就是ActivityThread,ActivityThread被创建的时候就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。