本系列出于AWeiLoveAndroid的分享,在此感谢,再结合自身经验查漏补缺,完善答案。以成系统。
Android基础知识点
- 四大组件是什么
http://blog.csdn.net/shiretan/article/details/55053857
Activity Service BroadCastReceiver ContentProvider
- 四大组件的生命周期和简单用法
- Activity之间的通信方式
http://blog.csdn.net/a_running_wolf/article/details/48813995
http://blog.csdn.net/a_running_wolf/article/details/48826495
Intent 静态变量 全局对象 SharePreference File Sqlite Service
- Activity各种情况下的生命周期
http://blog.csdn.net/woshimalingyi/article/details/50961380
配置改变(例如横竖屏切换) configChanges(改变不重建) 异常退出 onSaveInstance
onRestoreInstance
A onPause后B onResume
自己的Dialog是自己的View,不影响生命周期,其它的Dialog才会影响
- 横竖屏切换的时候,Activity 各种情况下的生命周期
http://blog.csdn.net/hzw19920329/article/details/51345971
... onSaveInstanceState->onStop... ---> onStart->onRestoreInstance
- Activity与Fragment之间生命周期比较
http://blog.csdn.net/u012702547/article/details/50253955
- Activity的生命周期&finish方法&back键&home键&dialog对话框
- 两个Activity 之间跳转时必然会执行的是哪几个方法?
http://blog.csdn.net/m_xiaoer/article/details/72881082
一般情况下比如说有两个activity,分别叫A,B。
当在A 里面激活B 组件的时候, A会调用onPause()方法,然后B调用onCreate() ,onStart(), onResume()。
这个时候B覆盖了A的窗体, A会调用onStop()方法。
如果B是个透明的窗口,或者是对话框的样式, 就不会调用A的onStop()方法。
如果B已经存在于Activity栈中,B就不会调用onCreate()方法。
- Activity的四种启动模式对比
http://blog.csdn.net/mynameishuangshuai/article/details/51491074
- standard 每次启动一个Activity都会重写创建一个新的实例
- singleTop 栈顶复用模式,已经位于栈顶,那么这个Activity不会被重写创建,同时它的onNewIntent方法会被调用
- standard和singleTop启动模式都是在原任务栈中新建Activity实例,不会启动新的Task,即使你指定了taskAffinity属性。
- singleTask 栈内复用模式,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。
其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。
两个不同App中的Activity设置为相同的taskAffinity,这样虽然在不同的应用中,但是Activity会被分配到同一个Task中去。
singleInstance-全局唯一模式,具备singleTask模式的所有特性外,单独占用一个Task栈,系统中具有全局唯一性。
- Activity状态保存于恢复
- fragment各种情况下的生命周期
- Fragment状态保存startActivityForResult是哪个类的方法,在什么情况下使用?
http://blog.csdn.net/barryhappy/article/details/53229238
Fragment所发起的请求,都通过一个映射,把最终的requestCode变成了一个大于0xffff的值。
- fragment之间传递数据的方式?
https://www.jianshu.com/p/f87baad32662
setData() 接口回调 EventBus
- Activity 怎么和Service 绑定?
http://blog.csdn.net/liyuchong2537631/article/details/48440911
- service和activity怎么进行数据交互?
- Service的开启方式,Service 的生命周期
- 谈谈你对ContentProvider的理解
- 请描述一下广播BroadcastReceiver的理解
http://blog.csdn.net/wl724120268/article/details/51607112
http://blog.csdn.net/hehe26/article/details/51674167
- 广播的分类
http://blog.csdn.net/wl724120268/article/details/51607112
有序,普通
- 广播使用的方式和场景
http://blog.csdn.net/yangzhaomuma/article/details/49817337
接受系统广播
进程间通讯
本地广播
- 在manifest 和代码中如何注册和使用BroadcastReceiver?
http://blog.csdn.net/yangzhaomuma/article/details/49817337
<receiver android:name="com.example.broadcasttest_csdn.MyReceiver"> <intent-filter > <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> </intent-filter> </receiver> IntentFilter iFilter=new IntentFilter(); iFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); bReceiver=new myBroadCast(); registerReceiver(bReceiver, iFilter);
- 本地广播和全局广播有什么差别/BroadcastReceiver,LocalBroadcastReceiver 区别?
http://blog.csdn.net/olevin/article/details/51993682
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); lbm.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO Handle the received local broadcast } }, new IntentFilter(LOCAL_ACTION)); lbm.sendBroadcast(new Intent(LOCAL_ACTION));
- AlertDialog,PopupWindow
http://blog.csdn.net/android_cmos/article/details/51223776
AlertDialog 非阻塞
PopupWindow 阻塞
- Application 和 Activity 的 Context 对象的区别
- Android动画
- 如何导入外部数据库?
https://www.cnblogs.com/xiaowenji/archive/2011/01/03/1925014.html
- LinearLayout、RelativeLayout、FrameLayout的特性及对比,并介绍使用场景。
http://blog.csdn.net/hejjunlin/article/details/51159419
- RelativeLayout需要横向纵向分别进行一次排序测量,相比LinearLayout的measure耗时要多些。如果LinearLayout中有weight属性,则也需要进行两次measure,但即便如此,应该仍然会比RelativeLayout的情况好一点。
- View的measure方法里对绘制过程做了一个优化,就是没啥变化时不重新measure,但是RelativeLayout是两次,可能有一次没完成,子View高度不等于ReleativeLayout导致View的这个优化不起作用。所以如果可以,尽量使用padding代替margin。
- LinearLayout里嵌套LinearLayout会加一倍measure。所以尽量少嵌套LinearLayout
- ReleativeLayout能更好的减少层级,层级越少效率越高,虽然单个是LinearLayout要高些,但是复杂的布局应当以减少层级为准
- 谈谈对接口与回调的理解
回调接口,回调方法,关注结果而不关注过程
- 介绍下SurfaceView
- RecycleView的使用
- 序列化的作用,以及Android两种序列化的区别
http://blog.csdn.net/wangchunlei123/article/details/51345130
- Serializable serialVersionUID transient
序列化和反序列化过程需要大量的I/O操作
适用于 序列化到本地或者通过网络传输- Parcelable 相比于Seriablizable具有更好的性能
public int describeContents(){}
public void writeToParcel(Parcel out,int flags){}
public static final Parcelable.Creator<User> CREATOR = {
createFromParcel(Parcel in){}
newArray(int size){}
}
适用于内存序列化
- 差值器
http://blog.csdn.net/pzm1993/article/details/77926373
负责控制动画变化的速率
继承TimeInterpolator 重写float getInterpolation(float input); 方法
- 估值器
http://blog.csdn.net/wuyuxing24/article/details/51591853
用来确定在动画过程中每时每刻动画的具体值
实现TypeEvaluator 重写public T evaluate(float fraction, T startValue, T endValue);
默认提供了如下几种估值器 ArgbEvaluator, FloatArrayEvaluator, FloatEvaluator, IntArrayEvaluator, IntEvaluator, PointFEvaluator, RectEvaluator。
- Android中数据存储方式
http://blog.csdn.net/amazing7/article/details/51437435
SharedPreferences
File (内部存储openFileOutput() openFileInput( ))+外部存储
Sqlite
ContentProvider
Net
Android源码相关分析
- Android动画框架实现原理
http://blog.csdn.net/harrain/article/details/53726960
View---invalidate()---mParent.invalidateChild() 递归绘制
ParentView 来不断调整 ChildView 的画布坐标系来实现的Animation 定义了一些动画属性
Transformation 持有一个矩阵 一个alpha
在具体的Animation的applyTransformation()方法中去改变值
View的draw中去应用实现动画效果
位移: t.getMatrix().setTranslate(dx, dy);
缩放: t.getMatrix().setScale(sx, sy);
旋转: t.getMatrix().setRotate(degrees);
渐变:t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
*Android属性动画原理/源码解析
http://blog.csdn.net/u013378580/article/details/51917884
上面文章不是最新的源码分析,但原理差不多
- 初始化
ValueAnimator.ofInt(int... values)---PropertyValuesHolder.ofInt("", values)---mKeyframes = KeyframeSet.ofInt(values);---Keyframe.ofInt()
ValueAnimator 外部api
PropertyValuesHolder 存储属性(mPropertyName)和贞(mKeyframes)
KeyframeSet 贞信息集合mFirstKeyframe,mLastKeyframe,
mKeyframes,mInterpolator, mEvaluator
Keyframe 一帧的信息- start()
addAnimationCallback(0)---Choreographer.postFrameCallback//新版本在此开启动画
startAnimation()---initAnimation();+setCurrentPlayTime()---animateValue().进行初始化设置
- Android各个版本API的区别
?
- requestlayout,onlayout,onDraw,DrawChild, invalidate区别与联系
http://blog.csdn.net/zoky_ze/article/details/54892971
- requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。 说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制 任何视图包括该调用者本身。
- onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局)
- 调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
- drawChild()去重新回调每个子视图的draw()方法
- View调运invalidate方法的实质是层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals方法,然后整个View树开始重新按照View绘制流程进行重绘任务。
- invalidate和postInvalidate的区别及使用
//postInvalidate 通过Handler会这样执行 case MSG_INVALIDATE: ((View) msg.obj).invalidate();
invalidate()得在UI线程中被调动
postInvalidate可以在非主线程中调用
invalidate的源码流程
invalidate---parent.invalidateChild(this, damage);---do {parent.invalidateChildInParent(location, dirty);}while---ViewRootImpl.invalidateChildInParent()---invalidateRectOnScreen(Rect dirty)---scheduleTraversals();---mTraversalRunnable.run(){performTraversals();}
- Activity-Window-View三者的差别
- 结论
Activity在onCreate时调用attach方法,在attach方法中会创建window对象(mWindow = new PhoneWindow(this, window, activityConfigCallback);)。window对象创建时并没有创建 DocerView 对象。用户在Activity中调用setContentView,然后调用window的setContentView,这时会检查DecorView是否存在,如果不存在则创建DecorView对象,然后把用户自己的 View 添加到 DecorView 中。
- Context源码分析
http://blog.csdn.net/qq_23547831/article/details/46481725
- Activity中ContextImpl实例化源码分析,其它类似
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { //省略... //创建Activity所数的ContextImpl对象,这里面最终是new的ContextImpl ContextImpl appContext = createBaseContextForActivity(r); //创建Activity示例 activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); //context持有activity实例,这是内存泄露的核心原理 appContext.setOuterContext(activity); } //Activity的attach方法 final void attach(...) { //Activity包装了ContextImpl实例 attachBaseContext(context); //省略... }
- 各个context.getResources()得到Resource是一个对象,因为ContextImpl中有如下代码
//单利创建资源对象 return ResourcesManager.getInstance().getResources(****);
- apk打包流程/项目构建流程
http://blog.csdn.net/qq_23547831/article/details/50634435
- AsyncTask源码分析
http://blog.csdn.net/qq_23547831/article/details/50803849
线程池 + Handler 模板方法模式threadPoolExecutor threadPoolExecutor = new >ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); private static InternalHandler sHandler; private final WorkerRunnable<Params, Result> mWorker; private final FutureTask<Result> mFuture;
- HandlerThread
http://blog.csdn.net/qq_23547831/article/details/50936584
这个类的作用是创建一个包含looper的线程。是手动实现的多线程+Handler的简化版
核心代码就是run方法中创建了Looperpublic void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
- IntentService源码
http://blog.csdn.net/qq_23547831/article/details/50958757
本质是一个机遇HandlerThread的Handler通过发送消息将执行的任务放到HandlerThread中去执行
- Zygote进程启动流程
http://blog.csdn.net/qq_23547831/article/details/51104873
- android系统中各种进程的启动方式:
init进程 –> Zygote进程 –> SystemServer进程 –>各种应用进程public static void main(String argv[]) { //创建孵化服务 ZygoteServer zygoteServer = new ZygoteServer(); //.... try { //... //设置DDMS可用 RuntimeInit.enableDdms(); // 启动分析孵化进程并初始化 SamplingProfilerIntegration.start(); boolean startSystemServer = false; String socketName = "zygote"; String abiList = null; boolean enableLazyPreload = false; //主要是解析main方法的参数获取是否需要启动SystemService进程, //获取abi列表,获取scoket连接名称 for (int i = 1; i < argv.length; i++) { if ("start-system-server".equals(argv[i])) { startSystemServer = true; } else if ("--enable-lazy-preload".equals(argv[i])) { enableLazyPreload = true; } else if (argv[i].startsWith(ABI_LIST_ARG)) { abiList = argv[i].substring(ABI_LIST_ARG.length()); } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { socketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } if (abiList == null) { throw new RuntimeException("No ABI list supplied."); } //为Zygote进程注册socket,这里注意SystemService与Zygote的通讯是通过Socket zygoteServer.registerServerSocket(socketName); if (!enableLazyPreload) { //... //初始化Zygote中需要的class类,系统资源,OpenGL,系统libraries,文字资源,webview; preload(bootTimingsTraceLog); //... } else { Zygote.resetNicePriority(); } //.... //通过Zygote fork出SystemServer进程 if (startSystemServer) { startSystemServer(abiList, socketName, zygoteServer); } zygoteServer.runSelectLoop(abiList); //关闭Socket连接 zygoteServer.closeServerSocket(); } catch (Zygote.MethodAndArgsCaller caller) { caller.run(); } catch (Throwable ex) { zygoteServer.closeServerSocket(); throw ex; } }
- SystemServer进程启动流程
http://blog.csdn.net/qq_23547831/article/details/51105171
- 首先会初始化一些系统变量,加载类库,创建Context对象,创建SystemServiceManager对象等之后才开始启动系统服务;
- SertemServer进程在尝试启动服务之前会首先尝试与Zygote建立socket通讯,只有通讯成功之后才会开始尝试启动服务;
- SystemServer进程将系统服务分为三类:boot服务,core服务和other服务,并逐步启动
- 创建的系统服务过程中主要通过SystemServiceManager对象来管理,通过调用服务对象的构造方法和onStart方法初始化服务的相关变量;服务对象都有自己的异步消息对象,并运行在单独的线程中;
- mActivityManagerService.systemReady(() -> startHomeActivityLocked 启动了Lanucher
- Launcher启动流程
http://blog.csdn.net/qq_23547831/article/details/51112031
- Zygote进程 –> SystemServer进程 –> startOtherService方法 –> ActivityManagerService的systemReady方法 –> startHomeActivityLocked方法 –> ActivityStackSupervisor的startHomeActivity方法 –> 执行Activity的启动逻辑,执行scheduleResumeTopActivities()方法。。。。
- 因为是隐士的启动Activity,所以启动的Activity就是在AndroidManifest.xml中配置catogery的值为:CATEGORY_HOME = "android.intent.category.HOME";
- LauncherActivity中是以ListView来显示我们的应用图标列表的,并且为每个Item保存了应用的包名和启动Activity类名,这样点击某一项应用图标的时候就可以根据应用包名和启动Activity名称启动我们的App了。
- 应用进程启动流程
http://blog.csdn.net/qq_23547831/article/details/51119333
启动四大组件之一便可启动应用进程
LauncherActivity---onListItemClick---startActivity(intent);-- mInstrumentation.execStartActivity()---ActivityManager.getService()
.startActivity()这里是AIDL机制,将与服务进程中的ActivityManagerService通讯---ActivityManagerService.startActivityAsUser()方法---ActivityStarter.startActivityMayWait()---startActivityLocked()---postStartActivityProcessing()启动进程---启动中的关键processClass=“android.app.ActivityThread”,也就是最终到ActivityThread的Main方法中
- Manifest解析
http://blog.csdn.net/qq_23547831/article/details/51203482
- android系统启动之后会解析固定目录下的apk文件,并执行解析,持久化apk信息,重新安装等操作;
- 解析Manifest流程:Zygote进程 –> SystemServer进程 –> PackgeManagerService服务 –> scanDirLI方法 –> scanPackageLI方法 –> PackageParser.parserPackage方法;
- 解析完成Manifest之后会将apk的Manifest信息保存在Settings对象中并持久化,然后执行重新安装的操作;
- apk安装流程
http://blog.csdn.net/qq_23547831/article/details/51210682
platform_packages_apps_packageinstaller源码,关注 清单配置文件,InstallStart(这是个Activity)负责引导而已,最终启动的PackageInstallerActivity 出来确认弹框---点击确定打开InstallInstalling(也是个Activity)
- 代码中执行intent.setDataAndType(Uri.parse(“file://” + path),”application/vnd.android.package-archive”);可以调起InstallStart判断好后再调起PackageInstallerActivity;
- PackageInstallerActivity主要用于执行解析apk文件,解析manifest,解析签名等操作;
- InstallInstalling主要用于执行安装apk逻辑,用于初始化安装界面,用于初始化用户UI。并调用PackageInstaller执行安装逻辑;
- InstallInstalling内注册有观察者,当安装完成之后接收广播,更新UI。显示apk安装完成界面;
- Activity的回调原理/activity的启动流程
http://blog.csdn.net/qq_23547831/article/details/51224992
- 应用进程---服务进程--孵化进程的复杂通讯
- 先之前的onPause---再新的create,start,resume,resume后通知服务进程对之前栈顶的Activity执行onStop,在onStop后saveState
- Activity的销毁流程
http://blog.csdn.net/qq_23547831/article/details/51232309
onPause(a) onRestart(b) onStart(b) onResume(b) onStop(a) onDestory(a)
CS架构的应用
- Application的创建流程
http://blog.csdn.net/qq_23547831/article/details/51252082
- ActivityThread.main方法–> ActivityManagerService.bindApplication方法 –> ActivityThread.handleBindApplication –> 创建Instrumentation,创建Application;
- 每个应用进程对应一个Instrumentation,对应一个Application;
- Instrumentation与Application都是通过java反射机制创建;
- Application创建过程中会同时创建一个ContextImpl对象,并建立关联;
- 布局加载流程
http://blog.csdn.net/qq_23547831/article/details/51284556
- 在启动时activity的attach方法创建Window对象 new PhoneWindow();一个activity对应一个window
- 在重写onCreate时必须调用super.onCreate,因为这里面进行了一些初始化操作,特别是activity.mCalled = true;否则会跑出did not call through to super.onCreate()的异常信息。
- setContentView实现是在PhoneWindow中。其流程是先generateDecor创建DecorView(extends FrameLayout),然后generateLayout在里面加载系统的布局并添加DecorView中,然后调用getDecorView().findViewById(id)取出布局中id为com.android.internal.R.id.content的View给mContentParent,然后通过mLayoutInflater.inflate(layoutResID, mContentParent);将setContentView中的布局加载并添加到mContentParent中。
- View的绘制流程源码解析
- ActivityThread在handleLaunchActivity中执行performLaunchActivity,attach时进行Window及WindowManager的创建,然后创建了DecorView加载了布局文件,然后执行handleResumeActivity方法
- 在handleResumeActivity在performResumeActivity后进行一些设置和初始化后调用r.activity.makeVisible();
- makeVisible()中wm.addView(mDecor, getWindow().getAttributes());其中wm为WindowManagerImpl。
- WindowManagerImpl 的addView委托给单利的WindowManagerGlobal进行addView
- WindowManagerGlobal持有三个集合mViews,mRoots,mParams。在其addView中会创建ViewRootImpl root,最终调用root.setView(view, wparams, panelParentView);将mDecorView与ViewRootImpl进行关联,而ViewRootImpl的主要作用就是绘制View
- root.setView中会执行requestLayout();
- requestLayout();中先检查当前是否为主线程,然后调用scheduleTraversals()
- scheduleTraversals中会启动mTraversalRunnable任务对象
- mTraversalRunnable中的run方法调用doTraversal(),doTraversal中调用performTraversals();这里也就是绘制的入口了
- performTraversals中以DecorView为跟,执行如下操作
- performMeasure---measure---onMeasure
- performLayout---layout ---onLayout
- performDraw--draw--drawSoftware--mView.draw---onDraw/dispatchDraw
- draw六部
- Draw the background
- If necessary, save the canvas' layers to prepare for fading
- Draw view's content
- Draw children
- If necessary, draw the fading edges and restore layers
- Draw decorations (scrollbars for instance)
- Dialog,Popwindow,Toast的显示源码分析
- Dialog
http://blog.csdn.net/qq_23547831/article/details/51289456
http://blog.csdn.net/qq_23547831/article/details/51303072
- Dialog和Activity的显示逻辑是相似的都是内部管理这一个Window对象,用WIndow对象实现界面的加载与显示逻辑;
- Dialog.show方法展示窗口,初始化Dialog的布局文件,Window对象等,然后执行mWindowManager.addView方法,开始执行绘制View的操作,并最终将Dialog显示出来;
- 通过调用WindowManager.removeViewImmediate方法,开始执行Window窗口的取消绘制流程;
- Window窗口的取消绘制流程,通过清空bitma撤销draw的执行效果,通过置空View撤销meature和layout的执行效果;
- Popwindow
http://blog.csdn.net/qq_23547831/article/details/51322574
- PopupWindow的界面加载绘制流程也是通过Window对象实现的;
- PopupWindow内部保存的mWindowManager对象通过ContextImpl中获取,并且取得的是WindowManagerImpl对象;
- PopupWindow通过为传入的View添加一层包裹的布局,并重写该布局的点击事件,实现点击PopupWindow之外的区域PopupWindow消失的效果;
- Toast
http://blog.csdn.net/qq_23547831/article/details/51374627
- Toast的show方法首先会初始化一个Toast对象,然后将内部对象TN与duration传递给NotificationManagerService,并在NotificationManagerService端维护一个Toast对象列表。
- NotificationManagerService接收到Toast的show请求之后,保存Toast对象并回调Toast.TN的show方法具体实现Toast窗口的显示逻辑。
- NotificationManagerService端在执行show方法执行会发送一个异步消息用于销毁Toast窗口,这个异步消息会在duration时间段之后发出,这样,在设置Toast显示的时间就会被传递到NotificationManagerService端,并在这段时间之后发送异步消息销毁Toast窗口。
- Toast窗口的显示与销毁机制与Activity、Dialog、PopupWIndow都是类似的,都是通过WIndow对象实现的。
- 谈谈对Volley的理解
/**
* 创建请求队列
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//...
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack(); //本质是HttpUrlConnection
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); //本质是HttpClient
}
}
//...
//创建请求Work对象
Network network = new BasicNetwork(stack);
//创建请求队列
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
//启动队列中的线程
queue.start();
return queue;
}
public void start() {
stop(); // 停止所有正在运行的
// 创建缓存线程
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
//启动缓存线程
mCacheDispatcher.start();
// 创建网络线程,默认4个
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
//启动网络线程
networkDispatcher.start();
}
}
public <T> Request<T> add(Request<T> request) {
//...
//若果不需要缓存加入网络队列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
//加入缓存请求队列
mCacheQueue.add(request);
}
public class CacheDispatcher extends Thread {
public void run(){
while(true){
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// 缓存中没有近网络
mNetworkQueue.put(request);
continue;
}
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
//缓存过期近网络
mNetworkQueue.put(request);
continue;
}
//有缓存,解析的结果,响应
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
mDelivery.postResponse(request, response);
}
}
}
public class NetworkDispatcher extends Thread {
……
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
//请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
//解析
Response<?> response = request.parseNetworkResponse(networkResponse);
//响应
mDelivery.postResponse(request, response);
}
}
}
//切到主线程响应,ResponseDeliveryRunnable在主线程
//new Queue时ExecutorDelivery(new Handler(Looper.getMainLooper())也就是通过主线程Handler响应
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
- android性能优化
- apk瘦身
- 图片压缩
- Analyze - -> Run Inspection by Name --> 输入 unused resource 删除无用的资源文件
- minifyEnabled混淆代码,shrinkResources去除无用资源
- 使用Svg格式的图片
- AndResGuard(微信开源的打包工具)
- 大项目使用插件化技术
- 数据库优化
- execSql省去了拼接sql语句的步骤,要比SqliteDatabase提供的insert,query,update,delete等函数效率高.但是有sql注入漏洞
- ContentValues和SQLiteStatement结合事务的方式进行操作。
- 布局优化
- RelativeLayout > LinearLayout 因为前者减少View树层级,不过要注意只有或者一层时LinearLayout更好,因为RelativeLayout的onMeasure会执行两次测量而LinearLayout执行一次
- <include>:重用,但可能会有多余的嵌套
- <merge> </merge>:多用于替换FrameLayout,作为include不足的辅助,比如LinearLayout中用include时,里面也是LinearLayout时可用merge替代
- <ViewStub/> 这是个没有大小的轻量级View,指定layout属性,本质是View先GONE(因为gone的不加载)一般用来一开始不显示,再某些情况下才显示的View,例如无网络时。主要作用是减少加载布局时的浪费。
- ConstraintLayout使用。比RelativeLayout在层级的控制上更加灵活多功能。
- FlexboxLayout使用。FlexboxLayout是更高级的LinearLayout
- 使用hierarchyviewer工具分析布局问题
- OOM优化-5R思想
- Reckon 计算(知道用了多少内存)
- Reduce 减少 (能用byte就别用long)
a. 更加轻量的数据结构 ArrayMap SpareMap > HashMap
b. android少用Enum,因为一个枚举值就是一个对象,耗内存。利用@interface+@IntDef/@StringDef替代enum
c. Bitmap对象: 一张图内存=w * h * byte * desinty比^2
d. 字符串拼接不用String- Reuse 重用
a. 列表ItemView复用
b. 合理使用线程池
c. LRU操控Bitmap
d. onDraw中减少new 对象操作。draw中频繁new 容易内存抖动- Recycle 回收
a. bitmap回收
b. 流的关闭,资源的释放如自定义View中TypeArray.recycle(),webview.destory()
c. 灵活使用软 弱 引用
d. 仿内存泄露(短不被长所引用,如静态context,静态view,单利持有context,activity中的异步内部类等)- Review 检查
a. LeakCanary
b. studio 的 android profiler- ANR
- 条件:KeyDispatchTimeout >5s; BroadcastQueue Timeout > 10s; ContentProvider Timeout; Service Timeout > 20s
- 注意: UI线程中不做耗时工作。子线程执行耗时工作,子线程Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
- 分析: /data/anr/目录下生成trace.txt文件
- 自定义View优化
- 降低刷新频率
a. draw中少new或者不new对象
b. 灵活运用invalidate(int l, int t, int r, int b),进行局部刷新
c. 避免不必要的requestLayout- 合理使用硬件加速:硬件加速适用于动画
GPUs非常擅长某些任务,例如测量,翻转,和平移位图类的图片。特别地,他们不擅长其他的任务,例如画直线和曲线。为了利用GPU加速度类,你应该增加GPU擅长的操作数量,和减少GPU不擅长的操作数量。- 状态的存储与恢复(onSaveInstanceState()+ onRestoreInstanceState)
- 低版本SDK如何实现高版本api?
- 用@TargeApi($API_LEVEL) 使可以编译通过, 不建议使用@SuppressLint("NewApi");
- 编码中结合向下的兼容包,做好版本的判断,不同的版本使用不同的方式。
- 描述一次网络请求的流程
http://blog.csdn.net/seu_calvin/article/details/53304406
DNS解析--建立TCP连接--发送请求--响应请求--解析请求结果
- HttpUrlConnection 和 okhttp关系
从Android 4.4起, 其HttpURLConnection的内部实现已经变为OkHttp
- Bitmap对象的理解
https://www.jianshu.com/p/3950665e93e6
- final类 无修饰符构造器
- 创建Bitmap = Bitmap.createBitmap() / BitmapFactory.decode()
- 颜色Bitmap.Config 8--8888--4444--565(能满足且比8888少一半占用空间) 影响一个像素的占用的字节
- 压缩Bitmap.CompressFormat JPEG(有损)-PNG(无损)-WEBP(有损,小但是耗时)
- Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)可裁剪,缩放,选装等
- BitmapFactory.Options 压缩,优化减少OOM的概率
inBitmap 复用
inJustDecodeBounds true后false 减少一次想内存的加载得宽高
inSampleSize 压缩计算
inPurgeable 4.1后 用时加载,不用时回收
- looper架构
https://www.jianshu.com/p/8656bebc27cb
ActivityThread,AMS,WMS的工作原理
自定义View如何考虑机型适配
自定义View的事件
AstncTask+HttpClient 与 AsyncHttpClient有什么区别?
LaunchMode应用场景
- singleTop适合接收通知启动的内容显示页面。
- singleTask适合作为程序入口点。
- singleInstance适合需要与程序分离开的页面。
- 请介绍下ContentProvider 是如何实现数据共享的?
- 说说Activity、Intent、Service 是什么关系
Activity 跳转到 Activity,Activity 启动 Service,Service 打开 Activity
都需要 Intent 表明跳转 的意图,以及传递参数,Intent 是这些组件间信号传递的承载者。
- SP是进程同步的吗?有什么方法做到同步?
https://www.jianshu.com/p/c15a63301592
在一个进程中,sharedPreference的commit apply都是原子操作。一般不会发生并发冲突。
在API Level>=11即Android 3.0可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享。但是实际中然并卵,不靠谱。
解决方案还是建议换种进程间通讯方式
- 谈谈多线程在Android中的使用
- Activity.runOnUiThread(Runnable)
- View.post(Runnable) ;View.postDelay(Runnable , long)
- Handler
- AsyncTask
- IntentService
- Thread
- 进程和 Application 的生命周期
- 前台进程 可见进程 服务进程 后台进程 空进程
- onCreate onTerminate onLowMemory onConfigurationChanged
- 封装View的时候怎么知道view的大小
onMearsure onSizeChanged
- SurfaceView原理
- RecycleView原理
https://blog.csdn.net/qq_23012315/article/details/50807224
- RecyclerView的measure及layout过程委托给了RecyclerView.LayoutManager
- 填充ItemView的算法为:向父容器增加子控件,测量子控件大小,布局子控件,布局锚点向当前布局方向平移子控件大小,重复上诉步骤至RecyclerView可绘制空间消耗完毕或子控件已全部填充。
- ItemView的大小是包含这个位置偏移量的
- 滑动过程=Scroller + Fling ,但是它们的实现最终其实是一样的
- Recycler的作用就是重用ItemView,对于不同状态的ItemView存储在了不同的集合中,比如有scrapped、cached、exCached、recycled,当然这些集合并不是都定义在同一个类里。
- RecyclerView.RecycledViewPool可以实现在不同的RecyclerView之间共享ItemView
- 4种针对数据集的操作,分别是ADD、REMOVE、UPDATE、MOVE
- AndroidManifest的作用与理解
http://www.cnblogs.com/liyuanjinglyj/p/manifest.html
https://blog.csdn.net/u012486840/article/details/52468931
- <manifest>
- android:sharedUserId/android:sharedUserLabel : 共享用户id,能够用其它进程数据,甚至在其它进程运行
- android:installLocation 安装位置,【auto】 先内后外,【internalOnly】只内,【preferExternal】先外后内
- <uses-permission>——应用程序的权限申请
- <permission>节点——自定义应用程序的访问权限,用来限制对本应用程序或其他应用程序的特殊组件或功能访问。
- <instrumentation>节点——应用的监控器,用于监控应用程序与系统交互,它会在应用程序组件实例化之前被实例化。这个节点在多数情况下用于单元测试。在studio中不怎么用了
- <application>
- android:backupAgent: 自定义BackupAgent,备份代理,dmgr工具,BackupManager(备份管理器),BackupAgentHelper(SharedPreferencesBackupHelper和 FileBackupHelper)
- android:allowClearUserData:用户是否能选择自行清除数据
- android:allowTaskReparenting:是否允许activity更换从属的任务
- android:hasCode:APP是否包含任何的代码,自身不会含有任何的代码
- android:presistent:否应该在任何时候都保持运行状态,系统应用程序才是有意义的
- android:process:进程名,同名同sharedUserId才有意义
- android:taskAffinity:Activity任务栈名
- <Activity>
- android:alwaysRetainTaskState:是否保留状态不变, 比如切换回home
- ...
- intent-filter
- android:priority:优先级
- 里面有 action category data(meta-data)
- <activity-alias> 是为activity创建快捷方式的,可用于动态更换桌面图标
- < Provider >
- android:authorities:标识这个ContentProvider,调用者可以根据这个标识来找到它
- <supports-screens> 在android1.6以后的新特性,支持多屏幕机制
- uses-configuration 与uses-feature:这两者都是在描述应用所需要的硬件和软件特性,以便防止应用在没有这些特性的设备上安装。
常见的一些原理性问题
- Handler机制和底层实现
- HandlerThread和IntentService的差别
- 关于Handler,在任何地方new Handler 都是什么线程下?
主线程 new Handler 因为ActivityThread中的Looper.prepareMainLooper();+Looper.loop();所以会在主线程执行
在子线程中 new Handler 如果没有执行Looper.preare()则会抛出Can't create handler inside thread that has not called Looper.prepare()异常,如果调用了,则在子线程
- ThreadLocal原理,实现及如何保证Local属性?
https://blog.csdn.net/androidzhaoxiaogang/article/details/6788489
- 概述:当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
- ThreadLocal【ThreadLocalMap【Entry数组】】,Thread中持有一个ThreadLocalMap。
- 1.8中 Thread中有一个ThreadLocalMap,ThreadLocal中set代码如下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //map中的key是当前Thread所对应的ThreadLocalMap本身 if (map != null) map.set(this, value); else createMap(t, value); }
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY];//持有一个Entry数组 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//数组的索引为key的HasCode的原子运算,nextHashCode.getAndAdd(HASH_INCREMENT),其中nextHashCodeAtomicInteger table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY);
}
- 请描述一下View事件传递分发机制
https://blog.csdn.net/carson_ho/article/details/54136311
- View刷新机制
- View绘制流程
https://blog.csdn.net/yanbober/article/details/46128379/
起点:Activity.handleLaunchActivity中performLaunchActivity后handleResumeActivity中performResumeActivity后activity.makeVisible()中WindowManagerImp.addView中WindowManagerGlobal.addView中ViewRootImpl.setView中requestLayout()中scheduleTraversals中mTraversalRunnable.run中doTraversal中performTraversals();
- 自定义ViewGroup不走onDraw的原理
if (!dirtyOpaque) onDraw(canvas);
final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&(mAttachInfo== null||!mAttachInfo.mIgnoreDirtyState);
protected void computeOpaqueFlags() {
// Opaque if://不透明的条件
// - Has a background 有 background
// - Background is opaque 背景是不透明
// - Doesn't have scrollbars or scrollbars overlay 没有滚动条或滚动条覆盖
}
//原因 LinearLayout构造方法中调用了setWillNotDraw(true),增加了WILL_NOT_DRAW标志
//解决方案
// 指定background
// 构造器setWillNotDraw(false)
- 自定义View如何提供获取View属性的接口?
回调
- Android代码中实现WAP方式联网
- AsyncTask机制
- AsyncTask原理及不足
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();//队列
/**
* CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));//核心线程数 2~4
* MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
* KEEP_ALIVE_SECONDS = 30;
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;//线程池
public AsyncTask(@Nullable Handler handler) {}//Handler
默认串行,一个一个执行。并行需手动executeOnExecutor
没有生命周期关联,容易已发内存泄露,可能会导致结果丢失
- 如何取消AsyncTask?
// 对任务的处理,立即终止,还是先完成当前的
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
- 为什么不能在子线程更新UI?
ViewRootImple中requestLayout方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
- ANR产生的原因是什么?
activity > 5s
BroadcastReceiver > 10s
Service > 20s
- oom是什么?
- Oom 是否可以try catch?为什么?
如果OOM的原因不是try语句中的对象(比如内存泄漏),那么在catch语句中会继续抛出OOM
- 内存泄漏
Leakcanary源码分析
https://blog.csdn.net/cloud_huan/article/details/53081120
- LeakCanay的入口是在application的onCreate()方法中声明的,其实用的就是Application的ActivityLifecycleCallbacks回调接口监听所有activity的onDestory()的,在这个方法进行RefWatcher.watch对这个对象进行监控。
- 具体是这样做的,封装成带key的弱引用对象,然后GC看弱引用对象有没有回收,没有回收的话就怀疑是泄漏了,需要二次确认。然后生成HPROF文件,分析这个快照文件有没有存在带这个key值的泄漏对象,如果没有,那么没有泄漏,否则找出最短路径,打印给我们,我们就能够找到这个泄漏对象了。
- LruCache
https://blog.csdn.net/maosidiaoxian/article/details/51393753
- LRU 最近最少使用,ABCBDCACA 最近最少使用的是B,第一个距离最后的A最远
- 数据结构,双向循环列表
- this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
- accessOrder是指定它的排序方式,当它为false时,只按插入的顺序排序,即新放入的顺序会在链表的尾部;而当它为true时,更新或访问某个节点的数据时,这个对应的结点也会被放到尾部。
public Entry<K, V> eldest(),它返回的是最老的结点,当accessOrder为true时,也就是最近最少使用的结点。
- ContentProvider的权限管理(解答:读写分离,权限控制)
https://blog.csdn.net/anddlecn/article/details/51733690
https://blog.csdn.net/hb8676086/article/details/50164947
//A声明
<provider
android:name=".PeopleContentProvider"
android:authorities="com.harvic.provider.PeopleContentProvider"
android:exported="true"
android:permission="com.harvic.contentProviderBlog"
android:readPermission="com.harvic.contentProviderBlog.read"
android:writePermission="com.harvic.cotentProviderBlog.write"/>
//A定义,同级处定义
<permission
android:name="com.harvic.contentProviderBlog.read"
android:label="provider pomission"
android:protectionLevel="normal" />
....
//B使用者要声明有 A定义的权限
<uses-permission android:name="com.harvic.contentProviderBlog.read"/>
- Uri、UriMatcher、ContentUris详解
//Uri 通用资源标志符
//content ---scheme
//com.example.testcp.FirstContentProvider -- android:authorities 主机
// users ---path 路径
Uri uri = Uri.parse("content://com.example.testcp.FirstContentProvider/users")
// UriMatcher 匹配Uri
//初始化
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//注册需要的Uri:
matcher.addURI("com.yfz.Lesson", "people", PEOPLE);
matcher.addURI("com.yfz.Lesson", "person/#", PEOPLE_ID);
//匹配
Uri uri = Uri.parse("content://" + "com.yfz.Lesson" + "/people");
//match方法匹配后会返回一个匹配码Code,即在使用注册方法addURI时传入的第三个参数。
int match = matcher.match(uri);
switch (match)
{
case PEOPLE:
return "vnd.android.cursor.dir/people";
case PEOPLE_ID:
return "vnd.android.cursor.item/people";
default:
return null;
}
// ContentUris 操作Uri
Uri uri = Uri.parse("content://com.yfz.Lesson/people")
//添加id:操作后结果为:content://com.yfz.Lesson/people/10
Uri resultUri = ContentUris.withAppendedId(uri, 10);
//获取id 下面结果为personid = 10
Uri uri = Uri.parse("content://com.yfz.Lesson/people/10")
long personid = ContentUris.parseId(uri);
- 如何通过广播拦截和abort一条短信?
ublic class SmsReceiver extends BroadcastReceiver {
// 当接收到短信时被触发
@Override
public void onReceive(Context context, Intent intent) {
// 如果是接收到短信
if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
// 取消广播(这行代码将会让系统收不到短信)
abortBroadcast();
StringBuilder sb = new StringBuilder();
// 接收由SMS传过来的数据
Bundle bundle = intent.getExtras();
// 判断是否有数据
if (bundle != null) {
// 通过pdus可以获得接收到的所有短信消息
Object[] pdus = (Object[]) bundle.get("pdus");
// 构建短信对象array,并依据收到的对象长度来创建array的大小
SmsMessage[] messages = new SmsMessage[pdus.length];
for (int i = 0; i < pdus.length; i++) {
messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
}
// 将送来的短信合并自定义信息于StringBuilder当中
for (SmsMessage message : messages) {
sb.append("短信来源:");
// 获得接收短信的电话号码
sb.append(message.getDisplayOriginatingAddress());
sb.append("\n------短信内容------\n");
// 获得短信的内容
sb.append(message.getDisplayMessageBody());
}
}
Toast.makeText(context, sb.toString(), 5000).show();
}
}
}
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<receiver android:name=".SmsReceiver" >
<intent-filter android:priority="800" >
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
广播是否可以请求网络?
// 4.0后网络都要子线程,而广播在主线程广播引起anr的时间限制是多少?
//10s计算一个view的嵌套层级
int i = 0;
private void getParents(ViewParent view){
if (view.getParent() == null) {
Log.v("tag", "最终==="+i);
return;
}
i++;
ViewParent parent = view.getParent();
Log.v("tag", "i===="+i);
Log.v("tag", "parent===="+parent.toString());
getParents(parent);
}
- Activity栈
https://blog.csdn.net/csdn_of_coder/article/details/76343031
- Android线程有没有上限?
开线程无上限,就是开的越多,程序耗内存越大/逻辑越混乱,很容易挂掉
- 线程池有没有上限?
自由指定
- ListView重用的是什么?
https://blog.csdn.net/xiaogutou1/article/details/47136099
活动视图池,废弃视图池,临时视图池,删除视图池
- Android为什么引入Parcelable?
- 一、对象为什么需要序列化
1.永久性保存对象,保存对象的字节序列到本地文件。
2.通过序列化对象在网络中传递对象。
3.通过序列化对象在进程间传递对象。- 二、当对象需要被序列化时如何选择所使用的接口
1.在使用内存的时候Parcelable比Serializable的性能高。Serializable中有大量io操作。
2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC(内存回收)。
3.Parcelable不能使用在将对象存储在磁盘上这种情况,因为在外界的变化下Parcelable不能很好的保证数据的持续性。
- 有没有尝试简化Parcelable的使用?
@Parcelize
data class Student(val id: String, val name: String, val grade: String) : Parcelable
开发中常见的一些问题
- ListView图片加载错乱的原理和解决方案
- 混合开发有了解吗?
?
知道哪些混合开发的方式?说出它们的优缺点和各自使用场景?(解答:比如:RN,weex,H5,小程序,WPA等。做Android的了解一些前端js等还是很有好处的);
屏幕适配的处理技巧都有哪些?
- 服务器只提供数据接收接口,在多线程或多进程条件下,如何保证数据的有序到达?
?
类似AsyncTask的思路,一个线程池中同一时间只能执行一个线程,基于一个队列,在上一个线程没有return前,不执行其它线程
- 动态布局的理解
http://www.jb51.net/article/77875.htm
代码的方式创建布局组件
- 怎么去除重复代码?
粒度最小化
- 画出 Android 的大体架构图
- Recycleview和ListView的区别
- 动态权限适配方案,权限组的概念
- Android系统为什么会设计ContentProvider?
- 下拉状态栏是不是影响activity的生命周期
Android下拉通知栏不会影响Activity的生命周期方法
- 如果在onStop的时候做了网络请求,onResume的时候怎么恢复?
- 没onDestory就不会断。onResume中也就无需恢复。再说也不建议在onStop中进行网络请求。太耗时不行。如果用线程,则在onDestory中不取消线程就就不会终端,但会导致内存泄露。如果是在主线程中现在直接就不允许。
- Bitmap 使用时候注意什么?
先预加载,压缩,用完回收,列表中进行缓存复用
- onSaveInstanceState机制
https://blog.csdn.net/qq_23547831/article/details/51464535
- onSaveInstanceState方法是Activity的生命周期方法,主要用于在Activity销毁时保存一些信息。
*当Activity只执行onPause方法时(Activity a打开一个透明Activity b)这时候如果App设置的targetVersion大于android3.0则不会执行onSaveInstanceState方法。- 当Activity执行onStop方法时,通过分析源码我们知道调用onSaveInstanceState的方法直接传值为true,所以都会执行onSaveInstanceState方法。
Bitmap的recycler()
Android中开启摄像头的主要步骤
获得摄像头Feature和写文件的权限
启动Intent进行拍照
在onActivityResult回调函数里面,对图片进行处理
- ViewPager使用细节,如何设置成每次只初始化当前的Fragment,其他的不初始化?
- 点击事件被拦截,但是想传到下面的View,如何操作?
view..getParent().requestDisallowInterceptTouchEvent(true);
- 微信主页面的实现方式
ViewPager+Fragment+ViewGroup
- 微信上消息小红点的原理
MsgView