主頁 > 移動端開發 > DRouter的底層實作淺析

DRouter的底層實作淺析

2021-12-09 09:21:40 移動端開發

前言

DRouter是滴滴乘客端自研的一套Android路由框架,基于平臺化解耦的思想,為組件間通信服務,該專案以功能全面、易用為原則,支持各種路由場景,在頁面路由、服務獲取和過濾、跨行程及跨應用、VirtualApk插件支持等方面都能提供多樣化的服務,目前已在滴滴乘客端、順風車、單車、國際化、滴滴定制車中控、滴滴車載屏等十多個滴滴的app內使用,得到各種場景的驗證,

接入方式與使用

接入

github上的專案地址:github.com/didi/DRoute…

在專案根目錄下的build.gradle下添加插件依賴:

buildscript {    
	dependencies {        
		classpath "io.github.didi:drouter-plugin-proxy:1.0.1"
	}
}

在主module的build.gradle檔案中應用插件:

plugins {
    id 'com.didi.drouter'
}

在主module的build.gradle檔案中的dependencies中添加依賴:

dependencies {
		api "io.github.didi:drouter-api:2.1.0"
}

引入成功后,需要在Application中進行初始化:

DRouter.init(application);

使用方法

這里只做簡述,詳情參考官方檔案:github.com/didi/DRoute…

Activity、Fragment、View

靜態注冊

Activity、Fragment、View只支持靜態跳轉,在Activity注冊路由地址:

@Router(scheme = "didi", host = "router", path = "/test1")
class TestActivity1 : AppCompatActivity()
發起頁面跳轉
            DRouter.build("didi://router/test1")
                .putExtra("tag", this.javaClass.name)
                .start(this, object : RouterCallback.ActivityCallback() {
                    // Activity回呼
                    override fun onActivityResult(resultCode: Int, data: Intent?) {
                    }
                })

Handler

Handler的典型應用場景是:

  1. 端外、Push、h5呼叫native代碼
  2. 無需下沉介面的組件化通信,有資料回傳能力
  3. 實作增強版eventbus

Handler支持靜態和動態兩種注冊方式,

靜態注冊
@Router(scheme = "didi", host = "router", path = "/sendOrder",
        thread = Thread.WORKER)   //指定執行執行緒
public class SendOrderHandler implements IRouterHandler {
    @Override
    void handle(@NonNull Request request, @NonNull Result result);
        // result可以添加資料,會回傳給呼叫方
        // 如果需要攔截后面的所有結點可以執行,默認不攔截
        request.getInterceptor().onInterrupt();
    }
}
動態定義和注冊

在內部注冊一個監聽者,不會重新實體化,類似EventBus,比EventBus增加了資料回傳的能力 如果注冊時使用了lifecycleOwner,會自動解注冊

// 動態注冊
IRegister register = 
       DRouter.register(
              RouterKey.build("/dynamic/handler").setLifecycleOwner(lifecycleOwner), 
              new IRouterHandler() {
                     @Override
                     public void handle(@NonNull Request request, @NonNull Result result) {
                     }
              });
// 解注冊,如果注冊時使用了生命周期,則可省
register.unregister();
發起導航
DRouter.build("didi://router/sendOrder")
       .setHoldTimeout(3000)         // 設定超時時間內未回傳則自動回呼callback
       .start(context, new RouterCallback() {
            @Override
            public void onResult(@NonNull Result result) {
                // 只有目標被釋放后才會回呼,且不會阻塞該執行緒
            }
       });

Hold

正常情況下Activity被start后以及RouterHandler的handle方法執行完成后,RouterCallback中的Result會立刻回傳,如果希望RouterCallback中的Result可以等待目標某個觸發時機然后才回傳Result,同時不阻塞當前執行緒,可以考慮暫存Result

應用場景
  • 啟動登錄頁,等待用戶真正登陸成功后回呼
  • 用戶端外冷啟動App,需要等待MainActivity的onResume回呼或某些事情準備好后再回呼
  • RouterHandler有耗時任務,希望等待執行完后再回傳給呼叫方,又不阻塞呼叫方
配置

Activity和RouterHandler增加hold引數,就啟用了HoldResult

異步Activity
@Router(scheme = "didi", host = "router", path = "/login"
        hold = "true")
public class ActivityLogin extends Activity {

    @Override
    protected void onCreate() {
        super.onCreate();
        Request request = RouterHelper.getRequest(this);
        Result result = RouterHelper.getResult(this);

        // 根據業務,在某個時間點觸發這個釋放(必須有)
        RouterHelper.release(this);
    }
}
異步RouterHandler
@Router(scheme = "didi", host = "router", path = "/sendOrder",
        hold = "true")
public class SendOrderHandler implements IRouterHandler {
    @Override
    void handle(@NonNull Request request, @NonNull Result result);

        // 根據業務,在某個時間點觸發這個釋放(必須有)
        RouterHelper.release(request);
    }
}
發起導航
DRouter.build("didi://router/sendOrder")
       .setHoldTimeout(3000)         // 設定超時時間內未回傳則自動回呼callback
       .start(context, new RouterCallback() {
            @Override
            public void onResult(@NonNull Result result) {
                // 只有目標被釋放后才會回呼,且不會阻塞該執行緒
            }
       });

Interceptor

攔截器可以在打開指定頁面時進行一些行為,比如:

  • 目標頁面需要某種權限,比如某些場景下需要判斷用戶是否已登錄
  • 目標頁面需要先進行某項操作,比如打開定位功能
定義攔截器

全域攔截器是指任意請求都會執行

@Interceptor(name = "interceptor1",          //可選,可以使用名字替代類耦合的方式來參考到
             priority = 1,                   //可選,優先級,值越大優先級越高
             global = true)                  //可選,是否是全域攔截器
public class LoginInterceptor implements IRouterInterceptor {
    @Override
    public void handle(@NonNull Request request) {

        if (isGo?) {
            request.getInterceptor().onContinue();
        } else {
            request.getInterceptor().onInterrupt();
        }
    }
}
應用到頁面
@Router(path = "/main", interceptor = LoginInterceptor.class)
public class MainActvity extends Activity {}

原始碼分析

初始化流程

上述為DRouter的初始化流程圖,上述為DRouter的初始化流程圖,DRouter的專案結構為:

  • drouter-api:直接供業務層呼叫
  • drouter-api-stub:定義了MetaLoader和它的實作類,在編譯期生成路由表代碼
  • drouter-plugin:真正執行DRouter構建流程的插件,執行RouterTask去創建RouterLoader的真正實作并處理注解,加載路由表,
  • drouter-plugin-proxy:是一個Gradle Task,主要任務是拉取最新版本的drouter-plugin.jar包

其中drouter-api是直接供業務層呼叫的module,在api module中,依賴了drouter-api-stub:

    compileOnly project(':drouter-api-stub')

只在編譯時有效,不會參與打包 可以在自己的moudle中使用該方式依賴一些比如com.android.support,gson這些使用者常用的庫,避免沖突,

drouter-api-stub中,定義了MetaLoader和它的實作類:RouterLoader、ServiceLoader和InterceptorLoader,而這些子類的方法都是空實作,在編譯期,會通過javaassit API和Gradle Transform API來實作具體的方法,

專案中直接參考的是drouter-plugin-proxy,根目錄下的build.gradle中:

classpath 'io.github.didi:drouter-plugin-proxy:1.0.1'

drouter-plugin-proxy是一個Gradle Task,主要任務是拉取最新版本的drouter-plugin.jar包

drouter-plugin內部是真正執行DRouter構建流程的插件,執行RouterTask去創建RouterLoader的真正實作并處理注解,加載路由表,

所以DRouter的初始化,可以從編譯和運行兩個時期去分析,

編譯期

通過Transform在專案編譯程序中在class編譯為dex的程序中,能夠獲取到所有的class類,并進行處理,DRouter用到了groovy插件 + Transform API在編譯期自動生成了路由表,首先,在drouter-plugin-proxy module中,創建了一個RouterPlugin:

class RouterPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        ProxyUtil.confirm(project)
        project.extensions.create('drouter', RouterSetting)
        project.android.registerTransform(new TransformProxy(project))
    }
}

RouterPlugin最后注冊了一個TransformProxy:

class TransformProxy extends Transform {
    // ... 
  	@Override
    void transform(TransformInvocation invocation) throws TransformException, 
  			InterruptedException, IOException {
        // 插件版本
        String pluginVersion = ProxyUtil.getPluginVersion(invocation)
        if (pluginVersion != null) {
            File pluginJar = new File(project.rootDir, ".gradle/drouter/drouter-plugin-${pluginVersion}.jar")
            if (!pluginJar.exists()) { // 若不存在,嘗試下載
                // ...
            }
            if (pluginJar.exists()) {
                URLClassLoader newLoader = new URLClassLoader([pluginJar.toURI().toURL()] as URL[], getClass().classLoader)
                Class<?> transformClass = newLoader.loadClass("com.didi.drouter.plugin.RouterTransform") //【1】
                ClassLoader threadLoader = Thread.currentThread().getContextClassLoader()
                Thread.currentThread().setContextClassLoader(newLoader)
                Constructor constructor = transformClass.getConstructor(Project.class)
                Transform transform = (Transform) constructor.newInstance(project)
                transform.transform(invocation)	
                Thread.currentThread().setContextClassLoader(threadLoader)
                return
            } else {
                ProxyUtil.Logger.e("Error: there is no drouter-plugin jar")
            }
        } else {
            ProxyUtil.Logger.e("Error: there is no drouter-plugin version")
        }
        copyFile(invocation)
    }
}

TransformProxy在下載最新的drouter-plugin插件,并在【1】處,創建了一個com.didi.drouter.plugin.RouterTransform物件,并執行了transform(invocation) ,查看RouterTransform的代碼:

class RouterTransform extends Transform {
    @Override
    void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {
        long timeStart = System.currentTimeMillis()
        SystemUtil.debug = project.drouter.debug
        File cacheFile = new File(tmpDir, "cache")
        boolean configChanged = SystemUtil.configChanged(project)
        boolean useCache = !isWindow && invocation.incremental && project.drouter.cache && cacheFile.exists() && !configChanged
        if (useCache) {
            cachePathSet.addAll(cacheFile.readLines())
        }
        if (!invocation.incremental) {
            invocation.outputProvider.deleteAll()
        }
        compilePath = new ConcurrentLinkedQueue<>(project.android.bootClasspath)
        for (TransformInput transformInput : invocation.inputs) {
            handleDirectory(invocation, transformInput)
            handleJar(invocation, transformInput)
        }
        File dest = invocation.outputProvider.getContentLocation("DRouterTable", TransformManager.CONTENT_CLASS,
                ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY) // 【2】
        (new RouterTask(project, compilePath, cachePathSet, useCache, dest, tmpDir, project.drouter, isWindow)).run()	// 【3】
        FileUtils.writeLines(cacheFile, cachePathSet)
        Logger.v("Link: https://github.com/didi/DRouter")
        Logger.v("DRouterTask done, time used: " + (System.currentTimeMillis() - timeStart) / 1000f  + "s")
    }
}

【2】處創建了一個DRouterTable目錄,供RouterTask使用,最終執行到RouterTask的run方法:

    void run() {
        StoreUtil.clear();
        JarUtils.printVersion(project, compileClassPath);
        pool = new ClassPool();
        classClassify = new ClassClassify(pool, setting);
        startExecute();
    }

    private void startExecute() {
        try {
            long timeStart = System.currentTimeMillis();
            for (File file : compileClassPath) {
                pool.appendClassPath(file.getAbsolutePath());
            }
            if (useCache) {
                loadCachePaths(cachePathSet);
            } else {
                loadFullPaths(compileClassPath);
            }
            timeStart = System.currentTimeMillis();
            classClassify.generatorRouter(routerDir); // 【4】
        } catch (Exception e) {
            JarUtils.check(e);
            String message = e.getMessage();
            if (message == null || !message.startsWith("Class:")) {
                e.printStackTrace();
            }
            throw new GradleException("DRouter: Could not generate router table\n" + e.getMessage());
        } finally {
            executor.shutdown();
            FileUtils.deleteQuietly(wTmpDir);
        }
    }

【4】處ClassClassify的generatorRouter方法:

    public void generatorRouter(File routerDir) throws Exception {
        for (int i = 0; i < classifies.size(); i++) {
            AbsRouterCollect cf = classifies.get(i);
            cf.generate(routerDir);
        }
    }

AbsRouterCollect有三個實作類:

  • RouterCollect
  • InterceptorCollect
  • ServiceCollect

ClassClassify在初始化時就添加了這三個:

public class ClassClassify {
		private List<AbsRouterCollect> classifies = new ArrayList<>();

    public ClassClassify(ClassPool pool, RouterSetting setting) {
        // 初始化時就加入了三種AbsRouterCollect
        classifies.add(new RouterCollect(pool, setting));
        classifies.add(new ServiceCollect(pool, setting));
        classifies.add(new InterceptorCollect(pool, setting));
    }
		// ...
}

這里以RouterCollect為例,RouterCollect的generate方法實作為:

    @Override
    public void generate(File routerDir) throws Exception {
      	// 創建RouterLoader類 
        CtClass ctClass = pool.makeClass(getPackageName() + ".RouterLoader");
        CtClass superClass = pool.get("com.didi.drouter.store.MetaLoader");
        ctClass.setSuperclass(superClass);

        StringBuilder builder = new StringBuilder();
        builder.append("public void load(java.util.Map data) {\n");
        for (CtClass routerCc : routerClass.values()) {
            try {
                StringBuilder interceptorClass = null;
                StringBuilder interceptorName = null;

                String uriValue = "";
                String schemeValue = "";
                String hostValue = "";
                String pathValue = "";
                Annotation annotation = null;
                String type;
                int thread = 0;
                int priority = 0;
                boolean hold = false;
                if (routerCc.hasAnnotation(Router.class)) {
                    uriValue = ((Router) routerCc.getAnnotation(Router.class)).uri();
                    schemeValue = ((Router) routerCc.getAnnotation(Router.class)).scheme();
                    hostValue = ((Router) routerCc.getAnnotation(Router.class)).host();
                    pathValue = ((Router) routerCc.getAnnotation(Router.class)).path();
                    thread = ((Router) routerCc.getAnnotation(Router.class)).thread();
                    priority = ((Router) routerCc.getAnnotation(Router.class)).priority();
                    hold = ((Router) routerCc.getAnnotation(Router.class)).hold();
                    annotation = getAnnotation(routerCc, Router.class);
                    if (checkSuper(routerCc, "android.app.Activity")) {
                        type = "com.didi.drouter.store.RouterMeta.ACTIVITY";
                    } else if (checkSuper(routerCc,
                            "android.support.v4.app.Fragment", "androidx.fragment.app.Fragment")) {
                        type = "com.didi.drouter.store.RouterMeta.FRAGMENT";
                    } else if (checkSuper(routerCc, "android.view.View")) {
                        type = "com.didi.drouter.store.RouterMeta.VIEW";
                    } else if (checkSuper(routerCc, "com.didi.drouter.router.IRouterHandler")) {
                        type = "com.didi.drouter.store.RouterMeta.HANDLER";
                    } else {
                        throw new Exception("@Router target class illegal, " +
                                "support only Activity/Fragment/View/IRouterHandler");
                    }
                } else {
                    pathValue = "/" + routerCc.getName().replace(".", "/");
                    type = "com.didi.drouter.store.RouterMeta.ACTIVITY";
                }
                // ... 
              	if (isAnyRegex) {
                    items.add("    put(\"" + uri + "\", " + metaBuilder + ", data); \n");
                    //builder.append("    put(\"").append(uri).append("\", ").append(metaBuilder).append(", data); \n");
                } else {
                    items.add("    data.put(\"" + uri + "\", " + metaBuilder + "); \n");
                    //builder.append("    data.put(\"").append(uri).append("\", ").append(metaBuilder).append("); \n");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Collections.sort(items);
        for (String item : items) {
            builder.append(item);
        }
        builder.append("}");

        Logger.d("\nclass RouterLoader" + "\n" + builder.toString());
        generatorClass(routerDir, ctClass, builder.toString());
    }

在專案主目錄(一般是app, 這里用的是官方demo)下的demo/build/intermediates/transforms/DRouter/debug/...中查看生成的class類:

public class RouterLoader extends MetaLoader {
    @Override
    public void load(Map var1) {
        var1.put("@@$$/activity/dynamic", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/dynamic", "com.didi.demo.handler.DynamicActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/activity/remote", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/remote", "com.didi.demo.remote.RemoteActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/activity/result", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/result", "com.didi.demo.activity.ActivityResultActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/activity/router_page_single", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/router_page_single", "com.didi.demo.fragment.RouterPageSingleActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/activity/router_page_stack", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/router_page_stack", "com.didi.demo.fragment.RouterPageStackActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/activity/router_page_viewpager", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/router_page_viewpager", "com.didi.demo.fragment.RouterPageViewPagerActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/activity/test3", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/test3", "com.didi.demo.activity.ActivityTest3", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, true));
        var1.put("@@$$/activity/webview", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/webview", "com.didi.demo.web.WebActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/fragment/second", RouterMeta.build(RouterMeta.FRAGMENT).assembleRouter("", "", "/fragment/second", FragmentSecond.class, new com_didi_demo_fragment_FragmentSecond(), (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/handler/test1", RouterMeta.build(RouterMeta.HANDLER).assembleRouter("", "", "/handler/test1", HandlerTest1.class, new com_didi_demo_handler_HandlerTest1(), (Class[])null, (String[])null, 0, 1, false));
        var1.put("@@$$/handler/test3", RouterMeta.build(RouterMeta.HANDLER).assembleRouter("", "", "/handler/test3", HandlerTest3.class, new com_didi_demo_handler_HandlerTest3(), (Class[])null, (String[])null, 2, 0, true));
        var1.put("@@$$/view/bottom", RouterMeta.build(RouterMeta.VIEW).assembleRouter("", "", "/view/bottom", BottomView.class, new com_didi_demo_view_BottomView(), (Class[])null, (String[])null, 0, 0, false));
        var1.put("@@$$/view/headview", RouterMeta.build(RouterMeta.VIEW).assembleRouter("", "", "/view/headview", HeadView.class, new com_didi_demo_view_HeadView(), (Class[])null, (String[])null, 0, 0, false));
        var1.put("didi@@router$$/handler/test2", RouterMeta.build(RouterMeta.HANDLER).assembleRouter("didi", "router", "/handler/test2", HandlerTest2.class, new com_didi_demo_handler_HandlerTest2(), (Class[])null, (String[])null, 0, 0, false));
        this.put("@@$$/activity/Test1_<Arg1>_<Arg2>", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/activity/Test1_<Arg1>_<Arg2>", "com.didi.demo.activity.ActivityTest1", (IRouterProxy)null, new Class[]{InnerInterceptor.class}, new String[]{"interceptor1", "interceptor2"}, 0, 0, false), var1);
        this.put("@@$$/fragment/first/.*", RouterMeta.build(RouterMeta.FRAGMENT).assembleRouter("", "", "/fragment/first/.*", FragmentFirst.class, new com_didi_demo_fragment_FragmentFirst(), (Class[])null, (String[])null, 0, 0, false), var1);
        this.put("@@$$/handler/.*", RouterMeta.build(RouterMeta.HANDLER).assembleRouter("", "", "/handler/.*", HandlerAll.class, new com_didi_demo_handler_HandlerAll(), (Class[])null, (String[])null, 0, 2, false), var1);
        this.put("didi@@www\\.didi\\.com$$/activity/test2", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("didi", "www\\.didi\\.com", "/activity/test2", "com.didi.demo.activity.ActivityTest2", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false), var1);
    }

    public RouterLoader() {
    }
}

編譯期對RouterLoader的load方法進行了修改,將帶有注解的路由資訊添加到了路由表中,

運行期

從DRouter的使用方法入手,我們首先看看DRouter在初始化的時候做了什么:

    public static void init(Application application) {
        SystemUtil.setApplication(application);	// 將application儲存到一個靜態變數中
        RouterStore.checkAndLoad(RouterStore.HOST, true); // 【5】RouterStore.HOST = "host"
    }

查看【5】部分的實作:

    public static void checkAndLoad(final String app, boolean async) {
        if (!loadRecord.contains(app)) {
            synchronized (RouterStore.class) {
                if (!loadRecord.contains(app)) {
                    loadRecord.add(app);
                    if (!async) {
                        Log.d(RouterLogger.NAME, "DRouter start load router table sync");
                        load(app);
                    } else {
                        new Thread("drouter-table-thread") {
                            @Override
                            public void run() {
                                Log.d(RouterLogger.NAME, "DRouter start load router table in drouter-table-thread");
                                load(app);
                            }
                        }.start();
                    }
                }
            }
        }

引數app是app唯一ID,若加載記錄中已存在,則不會重新加載路由表;引數async用來控制是同步還是異步加載路由表,真正的路由方法在load(app)中:

    // class RouterStore
		private static void load(String app) {
        boolean load;
        if (HOST.equals(app)) {  //  HOST = ”host“ 
            load = loadHostTable(); 	// 【6】
            initialized = true;
            latch.countDown();
        } else {
          	// 【7】
            load = loadPluginTable(app,
                    Pair.create("Router", routerMetas),
                    Pair.create("Interceptor", interceptorMetas),
                    Pair.create("Service", serviceMetas));
        }
        if (!load) {
            RouterLogger.getCoreLogger().e(
                    "DRouterTable in app \"%s\" not found, " +
                            "please apply drouter plugin first.", app);
        }
    }

當傳入的引數app等于HOST時,加載路由表的邏輯是loadHostTable(),若不相等,則是【7】處的方法,逐個分析:

    private static boolean loadHostTable() {
        try {
            new RouterLoader().load(routerMetas);
            new InterceptorLoader().load(interceptorMetas);
            new ServiceLoader().load(serviceMetas);
        }  catch (NoClassDefFoundError e) {
            return false;
        }
        return true;
    }

另一種加載路由表的邏輯:

	private static boolean loadPluginTable(String packageName, Pair... targets) {
        try {
            for (Pair<String, Map<?, ?>> target : targets) {
              	// 通過反射構造MetaLoader
                MetaLoader loader = (MetaLoader) ReflectUtil.getInstance(
                        Class.forName(String.format("com.didi.drouter.loader.%s.%sLoader",
                                packageName, target.first))
                );
                assert loader != null;
                loader.load(target.second);
            }
        } catch (ClassNotFoundException e) {
            return false;
        }
        return true;
    }

兩種方式最終都是呼叫了loader.load()方法,load方法在編譯期已經將所有帶有注解的內容自動生成到方法中了,整個DRouter初始化到此結束,

動態添加路由表

Handler支持動態添加路由表,動態加載和loadHostTable中用到的三個引數也有關系:

    // key is uriKey,value is meta, with dynamic
    // key is REGEX_ROUTER,value is map<uriKey, meta>
    private static final Map<String, Object> routerMetas = new ConcurrentHashMap<>();
    // key is interceptor impl or string name
    private static Map<Object, RouterMeta> interceptorMetas = new ConcurrentHashMap<>();
    // key is interface,value is set, with dynamic
    private static final Map<Class<?>, Set<RouterMeta>> serviceMetas = new ConcurrentHashMap<>();

routerMetas的另一種添加在register方法內:

    // 注冊動態handler,key是routerKey,value是handler實體
		@NonNull
    @MainThread
    public synchronized static IRegister register(final RouterKey key, final IRouterHandler handler) {
        //...
      	// 構建meta資料
        RouterMeta meta = RouterMeta.build(RouterMeta.HANDLER).assembleRouter(
                key.uri.getScheme(), key.uri.getHost(), key.uri.getPath(),
                (Class<?>) null, null, key.interceptor, key.interceptorName,
                key.thread, 0, key.hold);
        meta.setHandler(key, handler);
      	// 若包含正則運算式或占位符
        if (meta.isRegexUri()) {
            Map<String, RouterMeta> regexMap = (Map<String, RouterMeta>) routerMetas.get(REGEX_ROUTER);
            if (regexMap == null) {
                regexMap = new ConcurrentHashMap<>();
                routerMetas.put(REGEX_ROUTER, regexMap);
            }
            regexMap.put(TextUtils.getStandardRouterKey(key.uri), meta);
        } else {
            routerMetas.put(TextUtils.getStandardRouterKey(key.uri), meta);
        }
      	// 系結具有生命周期的物件銷毀時,解除注冊
        if (key.lifecycleOwner != null) {
            key.lifecycleOwner.getLifecycle().addObserver(new LifecycleObserver() {
                @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
                public void onDestroy() {
                    unregister(key, handler);
                }
            });
        }
        return new RouterRegister(key, handler);
    }

serviceMetas的添加資料方法也是register,區別是支持泛型:

  	// 注冊動態服務,key是serviceKey,value是服務實體  
		@NonNull
    @MainThread
    public synchronized static <T> IRegister register(final ServiceKey<T> key, final T service) {
        // ... 
        RouterMeta meta = RouterMeta.build(RouterMeta.SERVICE).assembleService(
                null, null,
                key.alias, key.feature, 0, Extend.Cache.NO);
        meta.setService(key, service);
        key.meta = meta;
        Set<RouterMeta> metas = serviceMetas.get(key.function);
        if (metas == null) {
            metas = Collections.newSetFromMap(new ConcurrentHashMap<RouterMeta, Boolean>());
            serviceMetas.put(key.function, metas);
        }
        metas.add(meta);
        if (key.lifecycleOwner != null) {
            key.lifecycleOwner.getLifecycle().addObserver(new LifecycleObserver() {
                @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
                public void onDestroy() {
                    unregister(key, service);
                }
            });
        }
        return new RouterRegister(key, service);
    }

interceptorMetas沒有動態添加邏輯,因為攔截器和Activity/Fragment/View一樣沒有動態注冊,

呼叫流程

呼叫方式為:

DRouter.build(scheme).start(context);

build方法:

    /**
     * Navigation to router
     * there will be only one activity match at most, but may be several router handler.
     * @param uri String
     * @return request
     */
    @NonNull
    public static Request build(String uri) {
        return Request.build(uri);
    }

build方法回傳了一個Request物件,查看Request.build(uri);的實作邏輯:

    public static Request build(String uri) {
        return new Request(uri == null ? Uri.EMPTY : Uri.parse(uri));
    }

Request的構造方法為:

    private Request(@NonNull Uri uri) {
        this.uri = uri;
        this.serialNumber = String.valueOf(counter.getAndIncrement());
        putExtra(REQUEST_BUILD_URI, uri.toString());
    }

然后再看看Request的start方法:

    public void start(Context context) {
        start(context, null);
    }

    public void start(Context context, RouterCallback callback) {
        this.context = context == null ? DRouter.getContext() : context;
        RouterLoader.build(this, callback).start(); //【8】
    }

真正邏輯在【8】處,先看RouterLoader的build方法:

    @NonNull
    static RouterLoader build(Request request, RouterCallback callback) {
        RouterLoader loader = new RouterLoader();
        loader.primaryRequest = request; // 保存request
        loader.callback = callback;			 // 保存callback
        return loader;
    }

RouterLoader的build方法回傳了一個loader物件,看看它的start方法:

    void start() {
        if (!TextUtils.isEmpty(primaryRequest.authority)) {
            startRemote();
        } else {
            startLocal();
        }
    }

根據request的authority屬性是否為空來判斷是遠程還是本地呼叫,authority的值是遠程行程的 ContentProvider 權限,可以在呼叫跳轉邏輯時進行設定:

            DRouter.build("/handler/test1")
                    .putExtra("1", 1)
                    .putExtra("2", new Bundle())
                    .putAddition("3", new ParamObject())
                    .setRemoteAuthority("com.didi.drouter.remote.demo.host")
                    .start(DRouter.getContext());

兩個關鍵方法: startRemote 和 startLocal

startLocal
    private void startLocal() {
        TextUtils.appendExtra(primaryRequest.getExtra(), TextUtils.getQuery(primaryRequest.getUri()));
        // 按順序,優先級優先,然后完全匹配優先
        Map<Request, RouterMeta> requestMap = makeRequest(); // 【9】
        if (requestMap.isEmpty()) {
            RouterLogger.getCoreLogger().w("warning: there is no request target match");
            new Result(primaryRequest, null, callback);
            ResultAgent.release(primaryRequest, ResultAgent.STATE_NOT_FOUND);
            return;
        }
        final Result result = new Result(primaryRequest, requestMap.keySet(), callback);
        if (requestMap.size() > 1) {
            RouterLogger.getCoreLogger().w("warning: request match %s targets", requestMap.size());
        }
        final boolean[] stopByRouterTarget = {false};
      	// 遍歷request
        for (final Map.Entry<Request, RouterMeta> entry : requestMap.entrySet()) {
            if (stopByRouterTarget[0]) {
                // one by one
                ResultAgent.release(entry.getKey(), ResultAgent.STATE_STOP_BY_ROUTER_TARGET);
                continue;
            }
          	// 攔截器優先處理
            InterceptorHandler.handle(entry.getKey(), entry.getValue(), new IRouterInterceptor.IInterceptor() {
                @Override
                public void onContinue() {
                    entry.getKey().interceptor = new IRouterInterceptor.IInterceptor() {
                        @Override
                        public void onContinue() {
                        }

                        @Override
                        public void onInterrupt() {
                            RouterLogger.getCoreLogger().w(
                                    "request \"%s\" stop all remains requests", entry.getKey().getNumber());
                            stopByRouterTarget[0] = true;
                        }
                    };
                    // 【10】it will release branch auto when no hold or no target
                    RouterDispatcher.start(entry.getKey(), entry.getValue(), result, callback);
                    entry.getKey().interceptor = null;
                }

                @Override
                public void onInterrupt() {
                    ResultAgent.release(entry.getKey(), ResultAgent.STATE_STOP_BY_INTERCEPTOR);
                }
            });
        }
    }

【9】將request按照順序和優先級進行排序,

    private Map<Request, RouterMeta> makeRequest() {
        Map<Request, RouterMeta> requestMap = new LinkedHashMap<>();
        // 從intent中獲取序列化資料
        Parcelable parcelable = primaryRequest.getParcelable(Extend.START_ACTIVITY_VIA_INTENT);
        if (parcelable instanceof Intent) { // intent
            primaryRequest.getExtra().remove(Extend.START_ACTIVITY_VIA_INTENT); // 洗掉額外資訊
            Intent intent = (Intent) parcelable;
            PackageManager pm = primaryRequest.getContext().getPackageManager();
            List<ResolveInfo> activities = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);  // 查找匹配的activities
            if (!activities.isEmpty()) { // 存在匹配的activity
                primaryRequest.routerType = RouterType.ACTIVITY;    // request的type設定為ACTIVITY
                requestMap.put(this.primaryRequest, RouterMeta.build(RouterType.ACTIVITY).assembleRouter(intent));   // 構建一個RouterMeta存入map
            }
        } else {
            List<RouterMeta> metas = getAllRouterMetas();   // 獲取所有的路由表資訊
            int index = 0;
            for (RouterMeta routerMeta : metas) {   // 開始遍歷
                // 將占位符鍵和值注入到捆綁包中
                if (!routerMeta.injectPlaceHolder(primaryRequest.getUri(), primaryRequest.extra)) {
                    continue;
                }
                // 構建request
                Request request = createBranchRequest(
                        this.primaryRequest, metas.size() > 1, routerMeta.getRouterType(), index++);
                requestMap.put(request, routerMeta);
            }
        }
        return requestMap;
    }

然后遍歷request集合,去執行request的攔截器,處理完后通過【10】RouterDispatcher進行分發:

class RouterDispatcher {
		static void start(Request request, RouterMeta meta, Result result, RouterCallback callback) {
        switch (meta.getRouterType()) {
            case RouterType.ACTIVITY:
                startActivity(request, meta, result, callback);
                break;
            case RouterType.FRAGMENT:
                startFragment(request, meta, result);
                break;
            case RouterType.VIEW:
                startView(request, meta, result);
                break;
            case RouterType.HANDLER:
                startHandler(request, meta, result, callback);
                break;
            default:
                break;
        }
    }
}

這里可以看出,只有Activity和handler支持callback,看下Activity:

    private static void startActivity(Request request, RouterMeta meta, Result result, RouterCallback callback) {
       	// ...
        boolean hasRequestCode = request.getExtra().containsKey(Extend.START_ACTIVITY_REQUEST_CODE);
        int requestCode = hasRequestCode? request.getInt(Extend.START_ACTIVITY_REQUEST_CODE) : 1024;
        if (context instanceof Activity && callback instanceof RouterCallback.ActivityCallback) { // 【11】
            ActivityCompat2.startActivityForResult((Activity) context, intent,
                    requestCode, (RouterCallback.ActivityCallback) callback);
        } else if (context instanceof Activity && hasRequestCode) {		// 【12】
            ActivityCompat.startActivityForResult((Activity) context, intent,
                    requestCode, intent.getBundleExtra(Extend.START_ACTIVITY_OPTIONS));
        } else {	// 【13】
            ActivityCompat.startActivity(context, intent, intent.getBundleExtra(Extend.START_ACTIVITY_OPTIONS));
        }
      	// 處理頁面影片
        int[] anim = request.getIntArray(Extend.START_ACTIVITY_ANIMATION); 
        if (context instanceof Activity && anim != null && anim.length == 2) {
            ((Activity) context).overridePendingTransition(anim[0], anim[1]);
        }
        result.isActivityStarted = true;
      	// hold處理
        if (meta.isHold() && callback != null) {
            RouterLogger.getCoreLogger().w("request \"%s\" will be hold", request.getNumber());
            Monitor.startMonitor(request, result); // 【14】
        } else {
            ResultAgent.release(request, ResultAgent.STATE_COMPLETE);
        }
    }

這里的【11】、【12】、【13】處,呼叫了不同的Activity啟動方法,當callback的型別為RouterCallback.ActivityCallback時,通過自定義的工具類ActivityCompat2.startActivityForResult啟動,而12、13則是呼叫系統的API直接startActivity:

    static void startActivityForResult(@NonNull final Activity activity,
                                       @NonNull final Intent intent, final int requestCode,
                                       RouterCallback.ActivityCallback callback) {
        final int cur = sCount.incrementAndGet();
        sCallbackMap.put(cur, new Pair<>(new WeakReference<>(activity), callback));
        final Active active;
        if (activity instanceof FragmentActivity) { // 創建一個Fragment,系結到activity上
            active = new HolderFragmentV4();
        } else {
            active = new HolderFragment();
        }
        active.getCompat().cur = cur;
        active.attach(activity, intent, requestCode); // 【15】
    }

這里如果Activity是FragmentActivity,創建HolderFragmentV4,否則HolderFragment,兩者都實作了Active介面,并呼叫了attach方法:

// public static class HolderFragmentV4 extends Fragment implements Active
        @Override
        public void attach(Activity activity, Intent intent, int requestCode) {
            this.intent = intent;
            this.requestCode = requestCode;
            FragmentManager fragmentManager = ((FragmentActivity)activity).getSupportFragmentManager();
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            transaction.add(this, TAG);
            transaction.commit(); // add fragment
        }
// public static class HolderFragment extends android.app.Fragment implements Active
        @Override
        public void attach(Activity activity, Intent intent, int requestCode) {
            this.intent = intent;
            this.requestCode = requestCode;
            android.app.FragmentManager fragmentManager = activity.getFragmentManager();
            android.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
            transaction.add(this, TAG);
            transaction.commit();
        }

兩個fragment型別的生命周期,都會呼叫ActivityCompat2的對應方法:

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            activityCompat2.onCreate(savedInstanceState);
        }

        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            activityCompat2.onActivityResult(getActivity(), resultCode, data);
        }

        @Override
        public void onSaveInstanceState(@NonNull Bundle outState) {
            super.onSaveInstanceState(outState);
            activityCompat2.onSaveInstanceState(outState);
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            activityCompat2.onDestroy();
        }

當生命周期呼叫到了onActivityResult時,也會呼叫activityCompat2的onActivityResult方法,真正對callback進行呼叫:

    private void onActivityResult(Activity activity, int resultCode, Intent data) {
        RouterCallback.ActivityCallback cb;
        Pair<WeakReference<Activity>, RouterCallback.ActivityCallback> pair = sCallbackMap.get(cur);
        if (pair != null && (cb = pair.second) != null) {
            RouterLogger.getCoreLogger().d("HoldFragment ActivityResult callback success");
            cb.onActivityResult(resultCode, data); // 【16】callback進行了呼叫
        }
        if (pair == null || pair.first == null || pair.first.get() != activity) {
	          // HoldFragment onActivityResult warn, for host activity changed, but still callback last host
        }
        sCallbackMap.remove(cur);
        active.remove();
    }

【16】處對callback進行了呼叫,

而【14】處的hold導致延遲執行callback,實際是通過一個handlerpostDelay訊息來延時處理的:

class Monitor {

    private static Handler timeoutHandler;

    static void startMonitor(final Request request, final Result result) {
        long period = request.holdTimeout;
        if (period > 0) {
            check();
            RouterLogger.getCoreLogger().d("monitor for request \"%s\" start, count down \"%sms\"",
                    request.getNumber(), period);
            timeoutHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    RouterExecutor.submit(new Runnable() {
                        @Override
                        public void run() {
                            ResultAgent.release(request, ResultAgent.STATE_TIMEOUT);
                        }
                    });
                }
            }, period);
        }
    }

    private static void check() {
        if (timeoutHandler == null) {
            synchronized (Monitor.class) {
                if (timeoutHandler == null) {
                    HandlerThread handlerThread = new HandlerThread("timeout-monitor-thread");
                    handlerThread.start();
                    timeoutHandler = new Handler(handlerThread.getLooper());
                }
            }
        }
    }
}

接下來是handler的實作:

    private static void startHandler(final Request request, final RouterMeta meta,
                                     final Result result, final RouterCallback callback) {
        // dynamic
        IRouterHandler handler = meta.getHandler();
        if (handler == null) {
            handler = meta.getRouterProxy() != null ? (IRouterHandler) meta.getRouterProxy().newInstance(null) : null;
        }
        if (handler != null) {
            final IRouterHandler finalHandler = handler;
            RouterExecutor.execute(meta.getThread(), new Runnable() { 
                @Override
                public void run() {
                    if (meta.isHold()) {
                        RouterLogger.getCoreLogger().w("request \"%s\" will hold", request.getNumber());
                    }
                    finalHandler.handle(request, result);
                    if (meta.isHold() && callback != null) {
                        Monitor.startMonitor(request, result); // 【17】 postDelay 延時呼叫
                    } else {
                        ResultAgent.release(request, ResultAgent.STATE_COMPLETE); // 【18】直接呼叫onResult
                    }
                }
            });
        } else {
            ResultAgent.release(request, ResultAgent.STATE_ERROR);
        }
    }

RouterExecutor是一個執行器,內部有一個執行緒池物件可以用于指定執行的執行緒,在runnable中,handler呼叫了handle方法,callback則是通過是否hold來判斷是否立刻執行,

在【18】處,為什么會立即執行callback:

    static void release(Request request, String reason) {
        if (request != null) {
            release(request.getNumber(), reason);
        }
    }

    private synchronized static void release(String requestNumber, String reason) {
        Result result = getResult(requestNumber);
        if (result != null) {
            if (result.agent.primaryRequest.getNumber().equals(requestNumber)) {
                // all clear
                for (String number : result.agent.branchRequestMap.keySet()) {
                    if (!result.agent.branchReasonMap.containsKey(number)) {
                        completeBranch(number, reason);
                    }
                }
            } else {
                // branch only
                completeBranch(requestNumber, reason);
            }
            // check and release primary
            if (result.agent.branchReasonMap.size() == result.agent.branchRequestMap.size()) {
                completePrimary(result);
            }
        }
    }

這里的兩個方法:

completeBranch:

    private synchronized static void completeBranch(String branchNumber, String reason) {
        Result result = numberToResult.get(branchNumber);
        if (result != null) {
            if (STATE_TIMEOUT.equals(reason)) {
               // 超時強制執行log
            }
            result.agent.branchReasonMap.put(branchNumber, reason);
            numberToResult.remove(branchNumber);
        }
    }

completePrimary

    private synchronized static void completePrimary(@NonNull Result result) {
        numberToResult.remove(result.agent.primaryRequest.getNumber());
        if (result.agent.callback != null) {
            result.agent.callback.onResult(result); // 【19】callback得到呼叫
        }
        if (!numberToResult.containsKey(result.agent.primaryRequest.getNumber())) {
            RouterLogger.getCoreLogger().d("Request finish ");
        }
    }

最后再分析一下View和Fragment的啟動:

    private static void startFragment(Request request, RouterMeta meta, Result result) {
        result.routerClass = meta.getRouterClass();
        if (request.getExtra().getBoolean(Extend.START_FRAGMENT_NEW_INSTANCE, true)) {
            Object object = meta.getRouterProxy() != null ?
                    meta.getRouterProxy().newInstance(null) : null;
            if (object instanceof Fragment) {
                result.fragment = (Fragment) object;
                result.fragment.setArguments(request.getExtra());  
            }
        }
        ResultAgent.release(request, ResultAgent.STATE_COMPLETE);
    }

    private static void startView(Request request, RouterMeta meta, Result result) {
        result.routerClass = meta.getRouterClass();
        if (request.getExtra().getBoolean(Extend.START_VIEW_NEW_INSTANCE, true)) {
            Object object = meta.getRouterProxy() != null ?
                    meta.getRouterProxy().newInstance(request.getContext()) : null;
            if (object instanceof View) {
                result.view = (View) object;
                result.view.setTag(request.getExtra());
            }
        }
        ResultAgent.release(request, ResultAgent.STATE_COMPLETE);
    }

View和Fragment的實作邏輯基本一致,創建一個物件保存到result中,

最后是遠程呼叫startRemote:

    private void startRemote() {
        Result result = new Result(primaryRequest, Collections.singleton(primaryRequest), callback);
        RemoteBridge.load(primaryRequest.authority, primaryRequest.resendStrategy,
                primaryRequest.lifecycleOwner != null ? new WeakReference<>(primaryRequest.lifecycleOwner) : null)
                .start(primaryRequest, result, callback);
    }

RemoteBridge的load方法保存了遠程呼叫的相關資料,start方法真正進行了呼叫:

    public void start(final Request request, final Result result, RouterCallback callback) {
        final RemoteCommand command = new RemoteCommand(RemoteCommand.REQUEST);
        command.bridge = RemoteBridge.this;
        command.resendStrategy = resendStrategy;
        command.lifecycle = lifecycle;
        command.uri = request.getUri().toString();
        command.extra = request.getExtra();
        command.addition = request.getAddition();
        if (callback != null) {
            command.binder = new IClientService.Stub() {
                @Override
                public RemoteResult callback(RemoteCommand resultCommand) {
                    result.extra = resultCommand.extra;
                    result.addition = resultCommand.addition;
                    // callback once, so release it
                    RouterHelper.release(request);
                    return null;
                }
            };
        } else {
            // no callback, so release immediately
            RouterHelper.release(request);
        }
        execute(command);
    }

底層原理是binder跨行程呼叫,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/377143.html

標籤:其他

上一篇:【Android】自定義 View 系列-ViewGroup

下一篇:Android 網路編程、決議JSON資料(11)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more