【Android Framework (十) 】- ContentProvider

news2024/10/1 9:43:07

文章目录

  • 知识回顾
    • 启动第一个流程init
    • Zygote的流程
    • system_server
    • ServiceManager
    • Binder
    • Launcher的启动
    • AMS
    • service
    • binderService
  • 前言
  • 源码分析
    • 1.使用方法
    • 2.ContentProvider实现类。
    • 3.使用方法
    • 4.注册Observer
    • 正文
  • 拓展知识
  • 总结


知识回顾

启动第一个流程init

1,挂载文件系统,创建文件目录 调用selinux_setup权限安全相关
2,初始化内存空间 初始化属性服务 创建Epoll 注册监听子进程重启异常操作等,对子进程进行线程守护
3,startPropertyServic 开启属性服务 进行监听
4,LoadBootScripts 加载init.rc文件 进行解析 调用do_class_start 文件开启service
5,Service::Start函数->fork子进程->并且执行app_process文件,开启了zygote

Zygote的流程

1,startVm函数注册jvm startReg函数注册jni环境唤起 Zygote.main
2,forkSystemServer
3,preload预加载class 系统资源
4,调用runSelectLoop函数循环等待客户端连接
5,有客户端的连接后调用processOneCommand()函数 fork进程,初始化进程,创建ProcessState初始化binder
6,根据请求的targetClass 执行Main函数

system_server

system_server是系统用来启动管理service的入口,比如我们常用的AMS、WMS、PMS等等都是它来创建的,system_server加载了framework-res.apk,接着调用startBootstrapServices、startCoreServices、startOtherServices开启了非常多的服务,以及开启了WatchDog监控service。

ServiceManager

它是一个服务的提供者,可以让应用获取到系统的各种服务,还有Binder机制。ServiceManager是Android系统为开发者提供的一个服务大管家,当开机之后,由内核态进入用户态之后,会启动system_server进程,在该进程里面会对AMS,PKMS,PMS等等进行创建。然后添加到ServiceManager中。SystemServer算是一个大管家,他整合了系统的各种服务,监控着我们服务,管理服务的周期。而ServiceManager只有一个功能就是提供binder通信,让应用可以获取到系统提供的服务。

Binder

是Android特有的一种通信方式。Android Binder的前身是OpenBinder,后来在OpenBinder的基础上开发了Android Binder。
Android基于Linux所以支持Linux原生的IPC通信机制:共享内存、Pipe、Socket。Binder是Android特有的。

性能上:稳定性: 安全:
Binder : 一次拷贝 c/s架构 客户端和服务端 稳定 内核层校验系统来保证通信安全
共享内存:0次 不稳定会有同步问题和并发死锁问题 自定义协议
管道pipe:需要两次拷贝 单管道 效率低只能读或者只能写 自定义协议
Socket:两次拷贝 c/s架构 不好的地方消耗性能握手和挥手 自定义协议

Launcher的启动

它是由system_server开启的,通过LauncherModel进行IPC通信(Binder)调用PackageManagerService的queryIntentActivities获取到所有应用的信息,然后绑定数据源到RecyclerView中,而它的点击事件则是通过ItemClickHandler来进行分发的

AMS

接着讲了AMS是如何开启应用进程的,首先我们从Launcher的点击开始,调用到Activity的startActivity函数,通过Instrumentation的execStartActivity经过两次IPC(1.通过ServiceManager获取到ATMS 2.调用ATMS的startActivity) 调用到AMS端在AMS端进行了一系列的信息处理,会判断进程是否存在,没有存在的话就会开启进程(通过Socket,给ZygoteServer发送信息),传入entryPoint为ActivityThread,通过Zygote来fork出来子进程(应用进程)调用ActivityThread.main,应用进程创建之后会调用到AMS,由AMS来attachApplication存储进程信息,然后告诉客户端,让客户端来创建Application,并在客户端创建成功之后 继续执行开启Activity的流程。客户端接收到AMS的数据之后会创建loadedApk,Instrumentation 以及Application调用attach(attachBaseContext),调用Instrumentation的callApplicationOncreate执行Application的Oncreate周期.

应用执行完Application的OnCreate之后 回到ATMS的attachApplication 接着调用 realStartActivityLocked 创建了ClientTransaction,设置callBack为LaunchActivityItem添加了stateRequest 为ResumeActivityItem,然后通过IApplicationThread 回到客户端执行这两个事务,调用了ActivityThread的scheduleTransaction 函数,调用executeCallBack 执行了LaunchActivityItem的execute 他会调用ActivityThread的 handleLaunchActivity,会创建Activity Context,通过Instrumentation.newActivity 反射创建Activity 并调用attach 绑定window 再通过Instrumentation的callActivityOnCreate执行Activity的onCreate,在Activity的onCreate中分发监听给ActivityLifecycleCallbacks。最后设置ActivityClientRecord的state为ON_CREATE。 接着执行executeLifecycleState,调用了cycleToPath,之前设置了state为ON_CREATE,所以会返回一个Int数组{2} 调用performLifecycleSequence会执行到ActivityThread的handleStartActivity分发ActivityLifecycleCallbacks,并且分发给Fragments,调用Instrumentation的callActivityOnStart 执行Activity的onStart并设置state为ON_START,接着执行ResumeActivityItem的execute,会调用到ActivityThread的handleResumeActivity,调用performResume 分发resume事件给ActivityLifecycleCallbacks,分发Fragments,调用Instrumentation的callActivityOnResume 执行Activity的onResume。 最后会调用ActivityClientRecord.activity.makeVisible 通过WindowManager 添加当前View 和 WMS(IPC) 通信 绘制UI,接着postResume 会执行 ATMS的activityresume 设置 AMS的Activity的状态。

service

接着讲了Service,介绍了Service是如何开启的,生命周期是怎么执行的,ANR 是如何弹出的。Service的启动分两种:startService和bindService。先回忆下startService:startService会调用到ContextImpl的startService,它会直接调用AMS的startService。在AMS这里会先检查Service是否可以执行(常驻内存、蓝牙、电源白名单允许直接启动服务),接着调用bringUpServiceLocked 判断是否需要隔离进程如果非隔离 就看是否已经启动进程 执行realStartServiceLocked,否则是隔离进程 直接开启新进程。开启成功之后会将ServiceRecord添加到mPendingServices中去。进程创建之后,会调用AMS的attachApplication 接着处理service(ActiveService),之前创建之后会添加到mPendingServices中,现在继续处理调用realStartServiceLocked来开启Service,在开启的过程中会埋入一个炸弹(给Handler发送一个SERVICE_TIMEOUT_MSG) 如果超时未处理会弹出ANR,然后调用app.thread.scheduleCreateService通知客户端创建反射Service、Context 调用onCreate 把Service存入到mService中,调用AMS的serviceDoneExecuting进行炸弹的拆除。 然后再埋炸弹 调用app.thread.scheduleServiceArgs 调用service.onStartCommand再通知AMS 拆除炸弹。 这样Service就运行起来了。

binderService

再回忆下bindService:首先ServiceConnection是无法跨进程通信的,所以在LoadedApk.java中帮我们封装了InnerConnection 它继承自Stub,也就是native层的JavaBBinder,然后通过bindIsolatedService将sd(InnerConnection)写入到Parcel中,调用AMS(BinderProxy)的transact进行IPC 通信,把InnerConnection存入到客户端的nodes中,以及给AMS的refs_by_node和refs_by_desc挂上InnerConnection 接着唤醒AMS,唤醒之后调用onTransact读取到传过来的sd并且包装成BpBinder(BinderProxy)返回,这样AMS就在bindIsolatedService的时候拿到了InnerConnection的BpBinder,接着AMS 通过pkms通过intent查找到服务端进程,创建AppBindRecord和ConnectionRecord,接着调用bringUpServiceLocked 看是否开启进程 如果没有开启就先开启进程,开启了进程之后 调用realStartServiceLocked 也埋了炸弹 接着调用 app.thread.scheduleCreateService通知客户端创建反射Service、Context 调用onCreate 把Service存入到mService中,调用serviceDoneExecutingLocked把炸弹拆除,再调用requestServiceBindingsLocked 去执行r.app.thread.scheduleBindService 执行服务端的onBind函数拿到了返回的Binder再调用AMS的publishService把服务发布到AMS(在服务端的进程创建binder_proc(server) 在AMS的refs_by_node 和refs_by_desc 挂上server),AMS的onTransact 中读取到服务端返回的server(Binder)包装成BpBinder,然后调用c.conn.connected(从refs_by_desc 和 refs_by_node 找到InnerConnection)调用connect 所以到了客户端进程会把server的BpBinder 挂在refs_by_desc和refs_by_node上边。再调用Servcie.onServiceConnected 之后拆除炸弹。

前言

ContentProvider是Android的四大组件之一,虽然他没有Broadcast和Service用的频繁。ContentProvider的作用是不同应用之间数据共享,提供一个统一的接口。例如我们我想让其他应用使用自己的数据 就需要使用ContentProvider。


源码分析

1.使用方法

process:指定ContentProvider的运行进程。
authorities:鉴权字符串列表。多个以;分割。必须至少指定一个。
multiprocess:该ContentProvider是否在其他应用中创建。默认是false。如果是true,每个进程都有自己的内容提供者对象,可以减少IPC通信的开销。

<provider
    android:multiprocess="false"
    android:name=".StudentContentProvider"
    android:exported="true"
    android:process=":provider"
    android:authorities="com.zenyyyng.student.provider" />

2.ContentProvider实现类。

//这里我使用了room
class StudentContentProvider : ContentProvider() {

    override fun onCreate(): Boolean {
        return true
    }


    companion object {
        private const val AUTHORITY = "com.zenyyyyng.student.provider"
        //URI的前缀必选是content://xxxx/table
        val URI_STUDENT: Uri = Uri.parse(
            "content://$AUTHORITY" + "/" + Student.TABLE_NAME
        )
    }


    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        val context = context ?: return null
        var cursor: Cursor =
            StudentDataBase.getInstance(context).getStudentDao()!!.getAll()
        cursor.setNotificationUri(context.contentResolver, uri)
        return cursor;
    }

    override fun getType(uri: Uri): String? {
        return null
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val context = context ?: return null
        val id = StudentDataBase.getInstance(context).getStudentDao()
            .insert(Student.fromContentValues(values))
        context.contentResolver.notifyChange(uri, null)
        return ContentUris.withAppendedId(uri, id)
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        val context = context ?: return 0
        val count = StudentDataBase.getInstance(context).getStudentDao()
            .deleteById(ContentUris.parseId(uri))
        context.contentResolver.notifyChange(uri, null)
        return count
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        val context = context ?: return 0
        val student = Student.fromContentValues(values);
        student.id = ContentUris.parseId(uri);
        val count = StudentDataBase.getInstance(context).getStudentDao()
            .update(student)
        context.contentResolver.notifyChange(uri, null);
        return count;
    }

    override fun applyBatch(operations: ArrayList<ContentProviderOperation>): Array<ContentProviderResult> {
        val context = context ?: return Array<ContentProviderResult>(1) { ContentProviderResult(0) }
        return StudentDataBase.getInstance(context)
            .runInTransaction(Callable<Array<ContentProviderResult>> {
                return@Callable super.applyBatch(
                    operations
                )
            })
    }


    override fun bulkInsert(uri: Uri, values: Array<ContentValues?>): Int {
        val context = context ?: return 0
        val studentDao = StudentDataBase.getInstance(context).getStudentDao()
        val notes = arrayOfNulls<Student>(values.size)
        var i = 0
        while (i < values.size) {
            notes[i] = Student.fromContentValues(values[i])
            i++
        }
        return studentDao.insertAll(notes).size
    }


}


3.使用方法

val studentValues = ContentValues()
studentValues.put("name", name)
studentValues.put("age", age)
requireActivity().contentResolver.insert(
    Uri.parse("content://com.iehshx.student.provider/student"),
    studentValues
)

4.注册Observer


requireActivity().contentResolver.registerContentObserver(
    Uri.parse("content://com.iehshx.student.provider/student"),true,object:ContentObserver(object:Handler(){}){
        override fun onChange(selfChange: Boolean, uri: Uri?) {
            super.onChange(selfChange, uri)
        }
    })

正文

ApplicationContentResolver mContentResolver = new ApplicationContentResolver(this, mainThread);
//获取到ContentResolver
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }
    
    //insert插入数据
    public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
                @Nullable ContentValues values) {
        Preconditions.checkNotNull(url, "url");

        try {//这里默认是null
            if (mWrapped != null) return mWrapped.insert(url, values);
        } catch (RemoteException e) {
            return null;
        }
        //获取到ContentProvider
        IContentProvider provider = acquireProvider(url);
        if (provider == null) {
            throw new IllegalArgumentException("Unknown URL " + url);
        }
        try {
            long startTime = SystemClock.uptimeMillis();
            Uri createdRow = provider.insert(mPackageName, url, values);
            long durationMillis = SystemClock.uptimeMillis() - startTime;
            maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
            return createdRow;
        } catch (RemoteException e) {
            // Arbitrary and not worth documenting, as Activity
            // Manager will kill this process shortly anyway.
            return null;
        } finally {
            releaseProvider(provider);
        }
    }
    
   
   public final IContentProvider acquireProvider(String name) {
        if (name == null) {
            return null;
        }
        //调用到ApplicationContentResolver
        return acquireProvider(mContext, name);
    }
    
    
    //调用到ActivityThread
        protected IContentProvider acquireProvider(Context context, String auth) {
            return mMainThread.acquireProvider(context,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), true);
        }



//ActivityThread.java
    public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
            //从本地找
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {//找到就返回
            return provider;
        }

        ContentProviderHolder holder = null;
        try {
            synchronized (getGetProviderLock(auth, userId)) {
            //通过AMS来找ContentProviderHolder
                holder = ActivityManager.getService().getContentProvider(
                        getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
            }
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        if (holder == null) {
            return null;
        }
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }
    
    //从本地找ContentProvider
    public final IContentProvider acquireExistingProvider(
            Context c, String auth, int userId, boolean stable) {
        synchronized (mProviderMap) {
            final ProviderKey key = new ProviderKey(auth, userId);
            //从map找 如果没有就返回null 第一次肯定是没有的
            final ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider;
            IBinder jBinder = provider.asBinder();
            if (!jBinder.isBinderAlive()) {
                handleUnstableProviderDiedLocked(jBinder, true);
                return null;
            }
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                incProviderRefLocked(prc, stable);
            }
            return provider;
        }
    }
    
    
    
    public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String callingPackage, String name, int userId,
            boolean stable) {
            //隔离进程的判断
        enforceNotIsolatedCaller("getContentProvider");
        if (caller == null) {
            throw new SecurityException(msg);
        }
        //获取到调用者的uid
        final int callingUid = Binder.getCallingUid();
        if (callingPackage != null && mAppOpsService.checkPackage(callingUid, callingPackage)
                != AppOpsManager.MODE_ALLOWED) {//权限校验
            throw new SecurityException("Given calling package " + callingPackage
                    + " does not match caller's uid " + callingUid);
        }
        //调用getContentProviderImpl
        return getContentProviderImpl(caller, name, null, callingUid, callingPackage,
                null, stable, userId);
    }
    
    
    
    private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, int callingUid, String callingPackage, String callingTag,
            boolean stable, int userId) {
        ContentProviderRecord cpr;
        ContentProviderConnection conn = null;
        ProviderInfo cpi = null;
        boolean providerRunning = false;

        synchronized(this) {
            long startTime = SystemClock.uptimeMillis();

            ProcessRecord r = null;
            if (caller != null) {
                r = getRecordForAppLocked(caller);
                if (r == null) {//如果调用者进程不存在 抛出异常
                    throw new SecurityException(
                            "Unable to find app for caller " + caller
                          + " (pid=" + Binder.getCallingPid()
                          + ") when getting content provider " + name);
                }
            }

            boolean checkCrossUser = true;

            checkTime(startTime, "getContentProviderImpl: getProviderByName");
            //从mProviderMap中获取 第一次是null的 
            cpr = mProviderMap.getProviderByName(name, userId);
            if (cpr == null && userId != UserHandle.USER_SYSTEM) {
                cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
                if (cpr != null) {
                    cpi = cpr.info;
                    if (isSingleton(cpi.processName, cpi.applicationInfo,
                            cpi.name, cpi.flags)
                            && isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {
                        userId = UserHandle.USER_SYSTEM;
                        checkCrossUser = false;
                    } else {
                        cpr = null;
                        cpi = null;
                    }
                }
            }
//providerRunning 默认是false
            if (providerRunning) {//不走这里 第一次是null 所以不会修改providerRunning
                cpi = cpr.info;
                String msg;

                if (r != null && cpr.canRunHere(r)) {
                    if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
                        throw new SecurityException("Content provider lookup "
                                + cpr.name.flattenToShortString()
                                + " failed: association not allowed with package " + msg);
                    }
                    checkTime(startTime,
                            "getContentProviderImpl: before checkContentProviderPermission");
                    if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
                            != null) {
                        throw new SecurityException(msg);
                    }
                    checkTime(startTime,
                            "getContentProviderImpl: after checkContentProviderPermission");
                    //ContentProvider 所在的进程已经存在,并且设置了可以在调用者进程创建  创建ContentProviderHolder
                    ContentProviderHolder holder = cpr.newHolder(null);
                    //设置一个空的provider
                    holder.provider = null;
                    return holder;
                }

                try {
                //从pkms中查找 差找不到返回null
                    if (AppGlobals.getPackageManager()
                            .resolveContentProvider(name, 0 /*flags*/, userId) == null) {
                        return null;
                    }
                } catch (RemoteException e) {
                }

               
                final long origId = Binder.clearCallingIdentity();

                checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");
        //provider已经运行的情况下 增加ContentProvider的引用计数
                conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
                        stable);
                if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
                    if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
                        checkTime(startTime, "getContentProviderImpl: before updateLruProcess");
                        mProcessList.updateLruProcessLocked(cpr.proc, false, null);
                        checkTime(startTime, "getContentProviderImpl: after updateLruProcess");
                    }
                }

                checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                final int verifiedAdj = cpr.proc.verifiedAdj;
                boolean success = updateOomAdjLocked(cpr.proc, true,
                        OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
                if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) {
                    success = false;
                }
                maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);
                checkTime(startTime, "getContentProviderImpl: after updateOomAdj");
                if (!success) {
                    boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
                    checkTime(startTime, "getContentProviderImpl: before appDied");
                    appDiedLocked(cpr.proc);
                    checkTime(startTime, "getContentProviderImpl: after appDied");
                    if (!lastRef) {
                        return null;
                    }
                    providerRunning = false;
                    conn = null;
                } else {
                    cpr.proc.verifiedAdj = cpr.proc.setAdj;
                }

                Binder.restoreCallingIdentity(origId);
            }

            if (!providerRunning) {//如果没有运行
                try {
                    //从pkms中查到ContentProvider的信息
                    cpi = AppGlobals.getPackageManager().
                        resolveContentProvider(name,
                            STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
                } catch (RemoteException ex) {
                }
                //找不到返回
                if (cpi == null) {
                    return null;
                }
                boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
                        cpi.name, cpi.flags)
                        && isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
                if (singleton) {
                    userId = UserHandle.USER_SYSTEM;
                }
                cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
                checkTime(startTime, "getContentProviderImpl: got app info for user");

                String msg;
                if ((msg = checkContentProviderAssociation(r, callingUid, cpi)) != null) {
                    throw new SecurityException("Content provider lookup " + name
                            + " failed: association not allowed with package " + msg);
                }
                checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
                        != null) {
                    throw new SecurityException(msg);
                }
                checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
                //AMS没有准备好 并且非系统进程
                if (!mProcessesReady
                        && !cpi.processName.equals("system")) {
                    throw new IllegalArgumentException(
                            "Attempt to launch content provider before system ready");
                }
                if (!mSystemProvidersInstalled && cpi.applicationInfo.isSystemApp()
                        && "system".equals(cpi.processName)) {
                    throw new IllegalStateException("Cannot access system provider: '"
                            + cpi.authority + "' before system providers are installed!");
                }

                if (!mUserController.isUserRunning(userId, 0)) {
                    return null;
                }
                //创建ComponentName
                ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
                //根据comp 查找ContentProviderRecord 第一次肯定找不到
                cpr = mProviderMap.getProviderByClass(comp, userId);
                checkTime(startTime, "getContentProviderImpl: after getProviderByClass");
                final boolean firstClass = cpr == null;
                if (firstClass) {//cpt为null 所以这里是true
                    final long ident = Binder.clearCallingIdentity();

                    try {
                    //从pkms获取到ApplicationInfo
                        ApplicationInfo ai =
                            AppGlobals.getPackageManager().
                                getApplicationInfo(
                                        cpi.applicationInfo.packageName,
                                        STOCK_PM_FLAGS, userId);
                        if (ai == null) {
                            return null;
                        }
                        //创建新的ApplicationInfo
                        ai = getAppInfoForUser(ai, userId);
                        //创建ContentProviderRecord对象
                        cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
                    } catch (RemoteException ex) {
                    } finally {
                        Binder.restoreCallingIdentity(ident);
                    }
                }
                
                //查看ContentProvider能否安装在调用者进程,如果可以返回一个空的ContentProvider
                if (r != null && cpr.canRunHere(r)) {
                    return cpr.newHolder(null);
                }
               
                final int N = mLaunchingProviders.size();
                int i;
                //看mLaunchingProviders中是否有我们需要的ContentProvider
                for (i = 0; i < N; i++) {
                    if (mLaunchingProviders.get(i) == cpr) {
                        break;
                    }
                }
            
                if (i >= N) {
                    final long origId = Binder.clearCallingIdentity();

                    try {
                        try {
                        //设置PackageStopState = false
                            AppGlobals.getPackageManager().setPackageStoppedState(
                                    cpr.appInfo.packageName, false, userId);
                        } catch (RemoteException e) {
                        } catch (IllegalArgumentException e) {
                        }
                        //获取ContentProvider运行的进程信息 ProcessRecord 
                        ProcessRecord proc = getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid, false);
                        if (proc != null && proc.thread != null && !proc.killed) {//如果进程存在
                            if (!proc.pubProviders.containsKey(cpi.name)) {//进程中发布的Providers没有就发布(存入map)
                                proc.pubProviders.put(cpi.name, cpr);
                                try {
                                //到ContentProvider的运行进程中执行scheduleInstallProvider
                                    proc.thread.scheduleInstallProvider(cpi);
                                } catch (RemoteException e) {
                                }
                            }
                        } else {//如果不存在
                        //开启进程
                            proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0,
                                    new HostingRecord("content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name)), false, false, false);
                            if (proc == null) {
                                return null;
                            }
                        }
                        //设置launchingApp
                        cpr.launchingApp = proc;
                        //把当前的ContentProviderRecord添加到mLaunchingProviders中
                        mLaunchingProviders.add(cpr);
                    } finally {
                        Binder.restoreCallingIdentity(origId);
                    }
                }

                if (firstClass) {//存入mProviderMap 以comp为key
                    mProviderMap.putProviderByClass(comp, cpr);
                }
                //以name为kay
                mProviderMap.putProviderByName(name, cpr);
                //引用计数+1
                conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
                        stable);
                if (conn != null) {
                //设置为等待状态
                    conn.waiting = true;
                }
            }
            grantEphemeralAccessLocked(userId, null /*intent*/,
                    UserHandle.getAppId(cpi.applicationInfo.uid),
                    UserHandle.getAppId(Binder.getCallingUid()));
        }
       
        final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT;
        boolean timedOut = false;
        synchronized (cpr) {
            while (cpr.provider == null) {
                if (cpr.launchingApp == null) {
                    return null;
                }
                try {
                    final long wait = Math.max(0L, timeout - SystemClock.uptimeMillis());
                    if (conn != null) {
                        conn.waiting = true;
                    }
                   //挂起调用者线程 直到ContentProvider安装完成
                    cpr.wait(wait);
                    if (cpr.provider == null) {
                        timedOut = true;
                        break;
                    }
                } catch (InterruptedException ex) {
                } finally {
                    if (conn != null) {
                        conn.waiting = false;
                    }
                }
            }
        }
        if (timedOut) {//超时
            String callerName = "unknown";
            synchronized (this) {
                final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
                if (record != null) {
                    callerName = record.processName;
                }
            }
            return null;
        }
        //返回ContentProviderHolder
        return cpr.newHolder(conn);
    }


由调用者进程调用到AMS中,如果ContentProvider已经发布了 通过canRunHere判断ContentProvider是否可以运行在调用者的进程中,如果允许 不会把已经发布的ContentProvider返回,而是返回新的ContentProviderHoder 但是会把provider设置成null。 如果不允许 设置OOMAdj 和 更新进程LRU 最终调用cpr.newHolder(conn)。如果没有发布还是会检查是否可以在调用者进程来创建,接下来看ContentProvider的进程是否创建,如果没有创建调用startProcessLocked启动进程。如果已经创建进程 调用ContentProvider进程的proc.thread.scheduleInstallProvider。 两种情况都会把ContentProvider添加到mLaunchingProviders和mProviderMap中。然后等待ContentProvider安装完成 唤醒。


        public void scheduleInstallProvider(ProviderInfo provider) {
            sendMessage(H.INSTALL_PROVIDER, provider);
        }
        
        
        case INSTALL_PROVIDER:  
            handleInstallProvider((ProviderInfo) msg.obj);  
            break;


    public void handleInstallProvider(ProviderInfo info) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
            installContentProviders(mInitialApplication, Arrays.asList(info));
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }
    
    
    
    private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();
        //从本进程中遍历providers 调用installProvider
        for (ProviderInfo cpi : providers) {
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
        //告诉AMS发布ContentProvider
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
    
//ContentProvider进程安装Provider
    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {//传递的holder是null
            Context c = null;
            //获取到ApplicationInfo 得到Context
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
                c = mInitialApplication;
            } else {
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                }
            }
            if (c == null) {
                return null;
            }

            if (info.splitName != null) {
                try {
                    c = c.createContextForSplit(info.splitName);
                } catch (NameNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }

            try {
            //获取到ClassLoader
                final java.lang.ClassLoader cl = c.getClassLoader();
                //获取到loadedApk
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                if (packageInfo == null) {
                    packageInfo = getSystemContext().mPackageInfo;
                }
                //反射创建Provider
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
                //调用getIContentProvider 获取到Transport 也就是ContentProviderNative 其实是一个BinderProxy 后续的通信都是通过他
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    return null;
                }
                //调用attach 最后会执行onCreate
                localProvider.attachInfo(c, info);
            } catch (java.lang.Exception e) {
                if (!mInstrumentation.onException(null, e)) {
                    throw new RuntimeException(
                            "Unable to get provider " + info.name
                            + ": " + e.toString(), e);
                }
                return null;
            }
        } else {//holder部位null的情况 可以直接使用
            provider = holder.provider;
        }

        ContentProviderHolder retHolder;

        synchronized (mProviderMap) {
            IBinder jBinder = provider.asBinder();
            if (localProvider != null) {//之前反射创建了 不是null
                ComponentName cname = new ComponentName(info.packageName, info.name);
                //获取ProviderClientRecord 第一次是null
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {
                    provider = pr.mProvider;
                } else {//创建 ContentProviderHolder 设置provider为反射创建的provider
                    holder = new ContentProviderHolder(info);
                    holder.provider = provider;
                    holder.noReleaseNeeded = true;
                    //通过installProviderAuthoritiesLocked创建ProviderClientRecord
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                    //存入mLocalProviders 和 mLocalProvidersByName
                    mLocalProviders.put(jBinder, pr);
                    mLocalProvidersByName.put(cname, pr);
                }
                //拿到ContentProviderHolder
                retHolder = pr.mHolder;
            } else {//为null 就是之前就有了
                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                if (prc != null) {
                    if (!noReleaseNeeded) {
                        incProviderRefLocked(prc, stable);
                        try {
                            ActivityManager.getService().removeContentProvider(
                                    holder.connection, stable);
                        } catch (RemoteException e) {
                            //do nothing content provider object is dead any way
                        }
                    }
                } else {
                //创建ProviderClientRecord
                    ProviderClientRecord client = installProviderAuthoritiesLocked(
                            provider, localProvider, holder);
                    if (noReleaseNeeded) {
                        prc = new ProviderRefCount(holder, client, 1000, 1000);
                    } else {
                        prc = stable
                                ? new ProviderRefCount(holder, client, 1, 0)
                                : new ProviderRefCount(holder, client, 0, 1);
                    }
                    mProviderRefCountMap.put(jBinder, prc);
                }
                retHolder = prc.holder;
            }
        }
        //返回ContentProviderHolder
        return retHolder;
    }


//创建ProviderClientRecord
    private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
            ContentProvider localProvider, ContentProviderHolder holder) {
        final String auths[] = holder.info.authority.split(";");
        final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);

        if (provider != null) {
            for (String auth : auths) {
                switch (auth) {
                    case ContactsContract.AUTHORITY:
                    case CallLog.AUTHORITY:
                    case CallLog.SHADOW_AUTHORITY:
                    case BlockedNumberContract.AUTHORITY:
                    case CalendarContract.AUTHORITY:
                    case Downloads.Impl.AUTHORITY:
                    case "telephony":
                        Binder.allowBlocking(provider.asBinder());
                }
            }
        }

        final ProviderClientRecord pcr = new ProviderClientRecord(
                auths, provider, localProvider, holder);
        for (String auth : auths) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord existing = mProviderMap.get(key);
            if (existing != null) {
            } else {
                mProviderMap.put(key, pcr);
            }
        }
        return pcr;
    }

遍历AMS传递过来的providers调用installProvider反射创建ContentProvider调用attachInfo 执行onCreate 创建ProviderClientRecord(持有provider和ContentProviderInfo 以及ContentProviderHolder) 存入mLocalProviders和mLocalProvidersByName 返回ContentProviderHolder.然后调用AMS的publishContentProviders进行发布。

    public final void publishContentProviders(IApplicationThread caller,
            List<ContentProviderHolder> providers) {
        if (providers == null) {
            return;
        }
        //判断隔离进程
        enforceNotIsolatedCaller("publishContentProviders");
        synchronized (this) {
        //获取到调用者的ProcessRecord
            final ProcessRecord r = getRecordForAppLocked(caller);
            if (r == null) {
                throw new SecurityException(
                        "Unable to find app for caller " + caller
                      + " (pid=" + Binder.getCallingPid()
                      + ") when publishing content providers");
            }

            final long origId = Binder.clearCallingIdentity();
            //遍历反射创建的providers
            final int N = providers.size();
            for (int i = 0; i < N; i++) {
                ContentProviderHolder src = providers.get(i);
                if (src == null || src.info == null || src.provider == null) {
                    continue;
                }
                //获取到ContentProviderRecord
                ContentProviderRecord dst = r.pubProviders.get(src.info.name);
                if (dst != null) {
                //创建ComponentName
                    ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                    //存入 mProviderMap
                    mProviderMap.putProviderByClass(comp, dst);
                    //获取到authority
                    String names[] = dst.info.authority.split(";");
                    //存入mProviderMap
                    for (int j = 0; j < names.length; j++) {
                        mProviderMap.putProviderByName(names[j], dst);
                    }
                    int launchingCount = mLaunchingProviders.size();
                    int j;
                    boolean wasInLaunchingProviders = false;
                    for (j = 0; j < launchingCount; j++) {
                        //将ContentProvider从mLaunchingProviders中移除
                        if (mLaunchingProviders.get(j) == dst) {
                            mLaunchingProviders.remove(j);
                            wasInLaunchingProviders = true;
                            j--;
                            launchingCount--;
                        }
                    }
                    //移除超时
                    if (wasInLaunchingProviders) {
                        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
                    }
                    r.addPackage(dst.info.applicationInfo.packageName,
                            dst.info.applicationInfo.longVersionCode, mProcessStats);
                    synchronized (dst) {
                        dst.provider = src.provider;
                        dst.setProcess(r);
                        //唤醒 唤醒之后返回cpt.newHolder
                        dst.notifyAll();
                    }
                    //更新OOMAdj
                    updateOomAdjLocked(r, true, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
                    maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
                            src.info.authority);
                }
            }

            Binder.restoreCallingIdentity(origId);
        }
    }

//在ActivityThread创建ContentProvider的Context的时候会走这里
c = context.createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE);

 public Context createPackageContext(String packageName, int flags)
            throws NameNotFoundException {
        return createPackageContextAsUser(packageName, flags, mUser);
    }
    
    
    public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
            throws NameNotFoundException {
        if (packageName.equals("system") || packageName.equals("android")) {//这里不走
            return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user,
                    flags, null, null);
        }
        //拿到loadedApk 从pkms中查询
        LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
        if (pi != null) {
        //创建ContentProvider进程的ContextImpl
            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,
                    flags, null, null);

            final int displayId = getDisplayId();

            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                    getDisplayAdjustments(displayId).getCompatibilityInfo()));
            if (c.mResources != null) {
                return c;
            }
        }

        throw new PackageManager.NameNotFoundException(
                "Application package " + packageName + " not found");
    }





    public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
            int flags, int userId) {
        final boolean differentUser = (UserHandle.myUserId() != userId);
        ApplicationInfo ai;
        try {
        //从pkms中拿到ApplicationInfo
            ai = getPackageManager().getApplicationInfo(packageName,
                    PackageManager.GET_SHARED_LIBRARY_FILES
                            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    (userId < 0) ? UserHandle.myUserId() : userId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }

        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                ref = null;
            } else if ((flags & Context.CONTEXT_INCLUDE_CODE) != 0) {
                ref = mPackages.get(packageName);
            } else {
                ref = mResourcePackages.get(packageName);
            }

            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (ai != null && packageInfo != null) {
                if (!isLoadedApkResourceDirsUpToDate(packageInfo, ai)) {
                    packageInfo.updateApplicationInfo(ai, null);
                }

                if (packageInfo.isSecurityViolation()
                        && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) {
                    throw new SecurityException(
                            "Requesting code from " + packageName
                            + " to be run in process "
                            + mBoundApplication.processName
                            + "/" + mBoundApplication.appInfo.uid);
                }
                return packageInfo;
            }
        }

        if (ai != null) {
            return getPackageInfo(ai, compatInfo, flags);
        }

        return null;
    }

//这样getClassLoader就获取到了ContentProvider应用的ClassLoader就可以通过反射创建调用了
final java.lang.ClassLoader cl = c.getClassLoader(); //获取到loadedApk LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); if (packageInfo == null) { packageInfo = getSystemContext().mPackageInfo; } //反射创建Provider localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name);
   


拓展知识

在这里插入图片描述

总结

1.获取ContentProvider
调用者进程调用到AMS中,如果ContentProvider已经发布了 通过canRunHere判断ContentProvider是否可以运行在调用者的进程中,如果允许 不会把已经发布的ContentProvider返回,而是返回新的ContentProviderHoder 但是会把provider设置成null。 如果不允许 设置OOMAdj 和 更新进程LRU 最终调用cpr.newHolder(conn)。如果没有发布还是会检查是否可以在调用者进程来创建,接下来看ContentProvider的进程是否创建,如果没有创建调用startProcessLocked启动进程。如果已经创建进程 调用ContentProvider进程的proc.thread.scheduleInstallProvider。 两种情况都会把ContentProvider添加到mLaunchingProviders和mProviderMap中。然后等待ContentProvider安装完成 唤醒。唤醒之后返回ContentProviderHolder。
2.安装ContentProvider
遍历AMS传递过来的providers调用installProvider反射创建ContentProvider调用attachInfo 执行onCreate 创建ProviderClientRecord(持有provider和ContentProviderInfo 以及ContentProviderHolder) 存入mLocalProviders和mLocalProvidersByName 返回ContentProviderHolder.然后调用AMS的publishContentProviders进行发布:存入mProviderMap中 一个是以ComponentName为key 一个是以authority为key(authority可以是多个;分割 但是dst是同一个)。然后唤醒 之前的getContentProviderImpl返回ContentProviderHolder,
,通过provider(IContentProvider BinderProxy)
再去进行ipc通信。 当然也可以在本进程创建。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/710842.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于eBPF技术的云原生可观测实践

** 基于eBPF技术的云原生可观测实践 ** eBPF技术是Linux内核3.15版本中引入的全新设计&#xff0c;自从2014年发布以来&#xff0c;一直都备受瞩目。在过去几年中&#xff0c;基于eBPF技术的实践和工程落地层出不穷&#xff0c;出现了爆发式的增长。2015年微软、Google、Face…

浏览器里的任意一个请求通过postman生成对应的代码

大多数情况下&#xff0c;我们都是不知道某个网站的get或者post请求以及其他请求&#xff08;比如说PUT请求等&#xff09;是该加哪些headers和cookie才能用代码请求成功&#xff0c;这时就需要下面的操作了。 浏览器里的任意一个请求通过postman生成对应的代码&#xff1a; …

外观模式的学习与使用

1、外观模式的学习 当你在开发软件系统时&#xff0c;系统内部的子系统可能会变得非常复杂&#xff0c;包含了许多相互关联的类和接口。在使用这些子系统时&#xff0c;你可能需要调用多个类和方法才能完成所需的功能。这样的复杂性可能导致代码难以维护、理解和使用。外观模式…

NSQ 实现逻辑探秘

1 什么是 NSQ NSQ 是一个消息队列中间件&#xff0c;用 go 实现&#xff0c;有如下特点&#xff1a; 分布式&#xff1a; 它提供了分布式的、去中心化且没有单点故障的拓扑结构&#xff0c;稳定的消息传输发布保障&#xff0c;能够具有高容错和高可用特性。 易于扩展&#xf…

【Echarts】echarts饼图、圆环图配置代码详解

前言 简介&#xff1a;本文将从头开始&#xff0c;带你快速上手 echarts最常用图例—饼图 准备&#xff1a;请自行先将echarts图例引入你的项目&#xff0c;本文不多介绍。&#xff08;引入 echarts教程&#xff1a;http://t.csdn.cn/mkTa4&#xff09; 心得&#xff1a;echar…

递归函数:

含义&#xff1a;自己调自己 递归三要素&#xff1a;定义函数、终止条件和等价关系式 小案例&#xff1a;排序 let arr1 [8, 8, 9, 13, 45, 8, 0, 1, 9, 66];//定义函数function quickSort(arr) {//终止条件if (arr.length < 1) return arr;const baseIndex Math.floor(…

十五、docker学习-docker核心docker数据卷

什么是数据卷 当我们在使用docker容器的时候&#xff0c;会产生一系列的数据文件&#xff0c;这些数据文件在我们删除docker容器时是会消失的&#xff0c;但是其中产生的部分内容我们是希望能够把它给保存起来另作用途的&#xff0c;Docker将应用与运行环境打包成容器发布&…

【游戏逆向】D3D HOOK实现透视讲解

实现目的: 目前大部分游戏通过Direct3D实现3D效果,通过挂钩相应函数,可以实现3D透视,屏幕挂字效果。而透视,屏蔽特定效果,设置透明在很多游戏(特别是FPS)中发挥着巨大的作用! 实现思路: [D3D] DirectX的功能都是以COM组件的形式提供的。在Direct3D中,主要通过采…

Unity新输入系统

1、导入新输入系统 &#xff08;1&#xff09; 这里改成.NET Framework&#xff0c;下面改成input system package(New) 2、使用新系统 &#xff08;1&#xff09; 在你的player物体上添加Player Input组件&#xff0c;然后CreateAction &#xff08;2&#xff09; 创建出…

连接器信号完整性仿真教程 五

本文将详细介绍CST电磁仿真的激励源&#xff08;Excitation Source&#xff09;及其设置。CST微波工作室根据具体应用和结构类型提供多种不同的激励源&#xff0c;总得来说包含激励端口&#xff08;Excitation Port&#xff09;和场源&#xff08;Field Sources&#xff09;。 …

3.Mysql子查询练习

1.子查询概述 子查询指一个查询语句嵌套在另一个查询语句内部的查询&#xff0c;内部的查询是外部查询的条件&#xff0c;这个特性从MySQL4.1开始引入 子查询(内查询)在主查询之前执行完成 子查询的结果被主查询(外查询)使用 注意事项&#xff1a; 子查询要包含在括号内 将子查…

Vue 时间格式转换

文章目录 将秒转换成简单时间格式方式一 表格渲染方式二 js转换 将时间转换为字符串方式一 年、月、日、时、分、秒、星期等信息方式二 返回多久之前的时间 将秒转换成简单时间格式 方式一 表格渲染 element-ui 表格为例&#xff0c;duration 单位为秒 <el-table-column …

逻辑回归精讲

一、从线性回归到逻辑回归 对于分类问题&#xff0c;我们该如何解决 可以通过线性回归阈值解决吗&#xff1f; 就上面的这张图而言&#xff0c;横轴蓝色的那条线是可以将正负样本区分开的。那我们再看一个例子 就上面的图而言&#xff0c;横轴蓝色的那条线无法将正负例正确划…

基于springboot的垃圾分类网站的设计与实现

系统设计 本垃圾分类网站主要包括三大功能模块&#xff0c;即用户功能模块和管理员功能模块、垃圾分类管理员功能模块。源码下载 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户是管理员&#xff0c;管理员登录后&#xff0c;通过管理员功能来管理后台系统…

SpringMVC (四) 数据处理及跳转

学习回顾&#xff1a;SpringMVC &#xff08;三&#xff09; RestFul和控制器 现在我们来看看SpringMVC参数接收处理和结果跳转处理吧&#xff01; 结果跳转方式 一、ModelAndView 设置ModelAndView对象 , 根据view的名称 , 和视图解析器跳到指定的页面 . 页面 : {视图解析器前…

测试员该如何向七大姑八大姨解释你的工作?

过年回家&#xff0c;走亲访友带来了一年未见的七大姑八大姨们&#xff0c;必不可少会出现一系列“灵魂拷问”&#xff0c;比如“二狗&#xff0c;在做啥工作呢&#xff1f;” 相比“有对象了么&#xff1f;”、“啥时候生娃&#xff1f;”等硬核话题&#xff0c;合理地向七大姑…

如果只能推荐3本关于python的书,你会推荐哪3本?

如果只能推荐3本Python书的话&#xff0c;我推荐这3本。 第一本&#xff1a;Python编程快速上手 让繁琐工作自动化 第2版  豆瓣评分8.9 本书是一本面向初学者的Python编程实用指南。本书不仅介绍了Python语言的基础知识&#xff0c;而且通过案例实践教读者如何使用这些知识和…

css自学框架之栅格化12格布局、flex布局下两端对齐,不满左对齐

flex基础知识 1.flex-direction 容器内元素的排列方向(默认横向排列) flex-direction:row; 沿水平主轴让元素从左向右排列flex-direction:column; 让元素沿垂直主轴从上到下垂直排列flex-direction:row-reverse;沿水平主轴让元素从右向左排列 2.flex-wrap 容器内元素的换行(…

java feign的使用详细步骤及okhttp的使用

1、首先创建一个feign的模块并配置依赖&#xff0c;如图&#xff1a; 1、引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency&g…

kali换源

sudo vim /etc/apt/sources.list&#xff08;打开sources.list 文件&#xff09; 官方源 deb http://http.kali.org/kali kali-rolling main non-free contrib deb-src http://http.kali.org/kali kali-rolling main non-free contrib 中科大源 deb http://mirrors.ustc.edu.cn…