主頁 > 移動端開發 > Flutter實踐深入分析之——FlutterActivity/Fragment原理流程分析

Flutter實踐深入分析之——FlutterActivity/Fragment原理流程分析

2021-10-27 10:06:10 移動端開發

文章目錄

  • 前言
  • FlutterActivity分析
  • FlutterActivityAndFragmentDelegate.Host分析
  • FlutterActivityAndFragmentDelegate分析
    • onAttach方法
    • onStart方法
    • onCreateView方法
  • FlutterSplashView分析
  • FlutterFragment分析
  • FlutterFragmentActivity分析
    • onCreate方法
    • 需要關聯的方法呼叫
  • 總結

前言

之前了解了flutter從啟動到view的創建流程,本文一起來分析下Flutter Android端FlutterActivity和FlutterFragment代碼流程分析,

Flutter Android在創建新Flutter專案的時候并沒有看到FlutterActivity/FlutterFragment,原因是Flutter Android的原始碼主要在maven端,也就是:io.flutter:flutter_embedding_xxx包中,配置地方是在flutter.gradle,可查看文章“深度了解Flutter APP的構建流程”,

如果使用flutter1.12版本之前FlutterActivity位于io.flutter.app.FlutterActivity,該類已經被Deprecated掉了,所以本文以我們專案中使用的版本flutter2.0.6為例,FlutterActivity位于:io.flutter.embedding.android.Flutter,下面我們直接進入FlutterActivity/FlutterFragment分析

FlutterActivity分析

在Flutter App Android端默認的MainActivity就是繼承自io.flutter.embedding.android.FlutterActivity,該類展示的是一個全屏的Flutter UI,它的主要作用是:

  1. 顯示Android的啟動影片
  2. 下手Flutter的啟動影片
  3. 配置狀態欄
  4. 選擇Dart執行應用程式包路徑入口點(默認是main()),選擇Flutter的初始路由
  5. 保存和回復實體狀態

首先我們來看類宣告部分

public class FlutterActivity extends Activity
    implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner {

FlutterActivityAndFragmentDelegate.Host分析

根據繼承關系可以發現,FlutterActivity直接繼承于Activity,同時實作了FlutterActivityAndFrameDelegate.Host,LifecycleOwner介面,Activity和LifecycleOwner不做解釋,我們來看FlutterActivityAndFragmentDelegate.Host介面

class FlutterActivityAndFragmentDelegate implements ExclusiveAppComponent<Activity> {
  
  /**
   * 擴展四個介面
   * SplashScreenProvider,提供一個 SplashScreen 以在 Flutter 初始化和渲染其第一幀時顯示
   * FlutterEngineProvider,提供和創建FlutterEngine
   * FlutterEngineConfigurator,在創建 FlutterEngine 后配置它,例如,添加插件
   * PlatformPlugin.PlatformPluginDelegate,PlatformPlugin 通常為 Flutter 框架請求的平臺功能實作了默認行為,允許實作者自定義 Flutter 框架呼叫彈出 Android 端導航堆疊時所需的行為,
   */
  interface Host
      extends SplashScreenProvider,
          FlutterEngineProvider,
          FlutterEngineConfigurator,
          PlatformPlugin.PlatformPluginDelegate {
    @NonNull
    Context getContext();

    // 是否從intent獲取路由
    @Nullable
    boolean shouldHandleDeeplinking();

    @Nullable
    Activity getActivity();

    // 獲取Activity或者Fragment的Lifecycle
    @NonNull
    Lifecycle getLifecycle();

		// 獲取宿主啟動Flutter攜帶的引數,通過intent決議,譬如enable-dart-profiling等,
    @NonNull
    FlutterShellArgs getFlutterShellArgs();

    // 獲取靜態快取的EngineId,如果沒有就回傳空,通過intent的cached_engine_id引數傳遞,
    @Nullable
    String getCachedEngineId();

    // 引擎是否跟宿主一起destory
    boolean shouldDestroyEngineWithHost();

    // detach flutter engine
    void detachFromFlutterEngine();

    // 獲取dart主入口,默認main,
    @NonNull
    String getDartEntrypointFunctionName();

    // 回傳app bundle dart代碼存在的路徑        
    @NonNull
    String getAppBundlePath();

    // 獲取初始路由地址,
    // 默認先從intent中決議route的值,沒有就去meta-data決議io.flutter.InitialRoute的值,沒有就回傳null,
    @Nullable
    String getInitialRoute();

    // 獲取Flutter的渲染模式,詳情見“[Flutter實踐深入分析中——FlutterView相關原始碼分析](https://beason.blog.csdn.net/article/details/120929369)”
    @NonNull
    RenderMode getRenderMode();

    // 獲取Transparency模式,用在FlutterView呈現FlutterEngine引擎渲染效果
    @NonNull
    TransparencyMode getTransparencyMode();

    // 提供一個Flutter開屏圖片,
    @Nullable
    SplashScreen provideSplashScreen();

    // 回傳一個用來渲染FlutterView的FlutterEngine引擎
    @Nullable
    FlutterEngine provideFlutterEngine(@NonNull Context context);

    // 創建和配置platform plugin,
    @Nullable
    PlatformPlugin providePlatformPlugin(
        @Nullable Activity activity, @NonNull FlutterEngine flutterEngine);

    // 根據需要配置FLutterEngine
    void configureFlutterEngine(@NonNull FlutterEngine flutterEngine);

    // 周期detached之前清除FlutterEngine配置
    void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine);

    // 如果 FlutterEngine 的插件系統已經連接到Activity,則回傳 true,允許插件與其互動
    boolean shouldAttachEngineToActivity();

    void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView);

    void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView);

    // Flutter UI開始繪制時呼叫
    void onFlutterUiDisplayed();

    // Flutter停止繪制時呼叫
    void onFlutterUiNoLongerDisplayed();

    // 恢復狀態
    boolean shouldRestoreAndSaveState();
  }
}

根據上面的原始碼分析可以知道FlutterActivityAndFragmentDelegate.Host提供的一系列相關引擎宣告周期、Flutter UI繪制周期、插件相關周期等方法可以知道,它是一個中間層,它是Flutter與Activity/Fragment之間的一個介面約定,而宿主Activity/Fragment幾乎不直接參加與Flutter之間的互動,這樣做的好處有:

  1. 利于解耦,Flutter/Fragment不直接與Flutter互動,利于維護
  2. Flutter版本迭代快,利于專案后期Flutter SDK的升級,而不必修改任何原生代碼
  3. 代碼的重復利用,后面會介紹Fragment和FlutterFragmentActivity,大部分都是復用了FlutterActivityAndFragmentDelegate內部相關邏輯

下面我們再來看看FlutterActivity的onCreate方法

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    switchLaunchThemeForNormalTheme();

    super.onCreate(savedInstanceState);

    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
    delegate.onRestoreInstanceState(savedInstanceState);

    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);

    configureWindowForTransparency();
    setContentView(createFlutterView());
    configureStatusBarForFullscreenFlutterExperience();
  }

詳細的介紹之前的文章有介紹這里不再詳述,見:Flutter時間深入分析之——Flutter APP啟動流程分析

FlutterActivityAndFragmentDelegate分析

從上面分析我們知道FlutterActivityAndFragmentDelegate.Host宣告了大量的Activity/Fragment與Flutter平臺通信的介面,那么FlutterActivityAndFragmentDelegate根據名字我們大概判斷它就是Activity/Fragment的一個代理類,也是直接與Activity/Fragment進行互動的類,

下面一個個來分析FlutterActivityAndFragmentDelegate的核心方法

onAttach方法

onAttach方法在Activity的onCreate方法和Fragment的onAttach方法中被呼叫,核心原始碼:

void onAttach(@NonNull Context context) {
    // 確保delegate沒有被release
    ensureAlive();

    if (flutterEngine == null) {
      setupFlutterEngine();
    }

    if (host.shouldAttachEngineToActivity()) {
      flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
    }
    platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
    host.configureFlutterEngine(flutterEngine);
  }

從上面的代碼分析,attach方法主要職責:

  1. 初始化Flutter系統,確保delegate沒有被release
  2. 獲取(根據EngineID獲取,目前還未正式使用)或者創建FlutterEngine
  3. 平臺插件獲取和自定義插件的注冊
  4. 引擎配置,

onStart方法

onStart方法在Activity和Fragment的onStart方法被呼叫,核心原始碼:

 // Activity和Fragment的onStart方法被呼叫,開始執行Dart 代碼的入口(Dart代碼如果沒有執行的情況下)
 void onStart() {
    Log.v(TAG, "onStart()");
    ensureAlive();
    doInitialFlutterViewRun();
  }

  /**
   * 首次在FlutterView中執行Dart代碼
   * 不支持在給定的FlutterView中重新加載、重啟Dart,如果Dart已經運行,那么什么都不做
   */
  private void doInitialFlutterViewRun() {
    if (host.getCachedEngineId() != null) {
      return;
    }

    if (flutterEngine.getDartExecutor().isExecutingDart()) {
      return;
    }
    // 各種優先級獲取初始跳轉dart的路由地址,
    String initialRoute = host.getInitialRoute();
    if (initialRoute == null) {
      initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
      if (initialRoute == null) {
        initialRoute = DEFAULT_INITIAL_ROUTE;
      }
    }
    // 通過引擎的NavigationChannel設定初始路由資訊,
    flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);

    //按照優先級獲取appBundlePath,默認從host獲取,無則從FlutterLoader獲取,
    String appBundlePathOverride = host.getAppBundlePath();
    if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
      appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
    }

    // 配置dart的entrypoint并且執行,默認入口函式名為main,可通過meta-data的io.flutter.Entrypoint修改,
    DartExecutor.DartEntrypoint entrypoint =
        new DartExecutor.DartEntrypoint(
            appBundlePathOverride, host.getDartEntrypointFunctionName());
    flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
  }

onCreateView方法

看完上面的邏輯,引擎,插件,環境都已經準備妥當,接下來就是創建View,onCreateView見名知意,大概職責如下:

  1. 在 View 層次結構中創建一個新的 FlutterView
  2. 添加一個 FlutterUiDisplayListener
  3. 將 FlutterEngine 附加到新的 FlutterView
  4. 回傳新的視圖層次結構

我們來看看onCreateView方法:

@NonNull
  View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    // 確保 delegate沒有吧release
    ensureAlive();

    // 根據不同的渲染模式選擇不同的View(SurfaceView或者TextureView)
    if (host.getRenderMode() == RenderMode.surface) {
      FlutterSurfaceView flutterSurfaceView =
          new FlutterSurfaceView(
              host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);

      // 創建FLutterView包含FlutterSurfaceView
      host.onFlutterSurfaceViewCreated(flutterSurfaceView);

      flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());

      // 創建FLutterView包含FlutterTextureView
      host.onFlutterTextureViewCreated(flutterTextureView);
      flutterView = new FlutterView(host.getActivity(), flutterTextureView);
    }

    // 添加監聽,當flutter渲染首幀時回呼,當 Flutter 開始和停止將像素渲染到 Android 視圖層次結構時呼叫的偵聽器,
    flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
    // 創建一個FlutterSplashView開屏view
    flutterSplashView = new FlutterSplashView(host.getContext());
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      flutterSplashView.setId(View.generateViewId());
    } else {
      flutterSplashView.setId(486947586);
    }
    // 顯示開屏圖示,即io.flutter.embedding.android.SplashScreenDrawable配置的drawable圖
    flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());

    // FlutterView與flutterEngine關聯attach,
    flutterView.attachToFlutterEngine(flutterEngine);
    // 回傳被開屏view包裹的FlutterView,
    return flutterSplashView;
  }

	//回呼監聽定義,回呼中本質是觸發呼叫對應FlutterActivity或FlutterFragment的FlutterActivityAndFragmentDelegate.Host實作方法,
  @NonNull
  private final FlutterUiDisplayListener flutterUiDisplayListener =
      new FlutterUiDisplayListener() {
        @Override
        public void onFlutterUiDisplayed() {
          //本質在FlutterActivity中呼叫Activity 5.0以上的reportFullyDrawn()安卓官方方法,
          host.onFlutterUiDisplayed();
        }

        @Override
        public void onFlutterUiNoLongerDisplayed() {
          //本質在FlutterActivity中呼叫,默認空實作,
          host.onFlutterUiNoLongerDisplayed();
        }
      };

到這里我們FlutterActivityAndFragmentDelegate的核心代碼基本上介紹完畢

從上面代碼分析結果看onCreateView的職責和邏輯都比較清晰,但是唯一有個疑問就是為什么最后回傳的不是FLutterView而是回傳的一個包含FLutterView的FlutterSplashView,下面我們再一起來分析下FlutterSplashView,看看是如何處理FlutterSplashView和FlutterView之間的關系,

FlutterSplashView分析

FlutterSplashView按照字面理解它的主要作用就是顯示 SplashScreen 的視圖,直到給定的 FlutterView 呈現其第一幀,

我們來看看它的核心流程:

final class FlutterSplashView extends FrameLayout {

  // 當 FlutterEngine 附加到給定 FlutterView 或從給定 FlutterView 分離時收到通知的偵聽器,
  @NonNull
  private final FlutterView.FlutterEngineAttachmentListener flutterEngineAttachmentListener =  ...
  // 當 Flutter 開始和停止將像素渲染到 Android 視圖層次結構時呼叫的偵聽器,
  @NonNull
  private final FlutterUiDisplayListener flutterUiDisplayListener = ...
    
  // 將啟影片面轉換為 Flutter UI
  @NonNull
  private final Runnable onTransitionComplete =
      new Runnable() {
        @Override
        public void run() {
          removeView(splashScreenView);
          previousCompletedSplashIsolate = transitioningIsolateId;
        }
      };

  /**
   * 把給定的splashScreen顯示在flutterView之上,直到flutterView的首幀渲染出來才過渡消失
   */
  public void displayFlutterViewWithSplash(
      @NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) {
    // 如果上一個FlutterView顯示中,先洗掉.
    if (this.flutterView != null) {
      this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
      removeView(this.flutterView);
    }
    // 復位
    if (splashScreenView != null) {
      removeView(splashScreenView);
    }

    // 把flutterView添加給當前FlutterSplashView,本質是一個FrameLayout,
    this.flutterView = flutterView;
    addView(flutterView);

    this.splashScreen = splashScreen;

    // 顯示一個splash screen開屏圖,
    if (splashScreen != null) {
      //如果flutterView未渲染出來則條件成立,
      if (isSplashScreenNeededNow()) {
        splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
        addView(this.splashScreenView);
        flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
      } else if (isSplashScreenTransitionNeededNow()) {
        splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
        addView(splashScreenView);
        transitionToFlutter();
      } else if (!flutterView.isAttachedToFlutterEngine()) {
        flutterView.addFlutterEngineAttachmentListener(flutterEngineAttachmentListener);
      }
    }
  }

  /**
   * 如果啟影片面正在過渡到 Flutter 體驗,則回傳 true
   */
  private boolean wasPreviousSplashTransitionInterrupted() {
    return flutterView.hasRenderedFirstFrame() && !hasSplashCompleted();
  }

  /**
   * 如果特定 Flutter 體驗的啟動 UI 已經完成,則回傳 true,
   */
  private boolean hasSplashCompleted() {
    return flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId() != null
        && flutterView
            .getAttachedFlutterEngine()
            .getDartExecutor()
            .getIsolateServiceId()
            .equals(previousCompletedSplashIsolate);
  }

  /**
   * 開屏過渡到flutterview顯示
   */
  private void transitionToFlutter() {
    transitioningIsolateId =
        flutterView.getAttachedFlutterEngine().getDartExecutor().getIsolateServiceId();
    splashScreen.transitionToFlutter(onTransitionComplete);
  }
}

從上面的代碼分析,FlutterSplashView邏輯并不復雜,主要就是在FlutterUI第一幀渲染之前顯示默認的Drawable,等到Flutter UI第一幀繪制的時候再remove掉,內部的實作邏輯都比較簡單這里不再介紹,下面繼續


FlutterFragment分析

上面分析完FlutterActivity的流程,我們再來看FlutterFragment就比較簡單了,

我們在使用FlutterFragment的時候,如果宿主Actvity而不是FlutterFragmentActivity的話,需要我們手動關聯一下方法,這些方法都被添加了@ActvivityCallThrough注釋宣告,如:

  1. onPostResume()
  2. onBackPressed()
  3. onRequestPermissionsResult(int, String[], int[]) ()}
  4. onNewIntent(Intent) ()}
  5. onUserLeaveHint()
  6. onTrimMemory(int)

此外,當為此 Fragment 的startActivityForResult 時,請務必呼叫 Fragment.startActivityForResult(Intent, int) 而不是 Activity.startActivityForResult(Intent, int),如果呼叫了該方法的 Activity 版本,則此 Fragment 將永遠不會收到其 Fragment.onActivityResult(int, int, Intent) 回呼,如果方便,可以考慮使用 FlutterActivity 而不是 FlutterFragment 來避免轉發呼叫的作業,

下面看核心原始碼

//FlutterFragment也實作了前面分析的FlutterActivityAndFragmentDelegate.Host介面,
public class FlutterFragment extends Fragment
    implements FlutterActivityAndFragmentDelegate.Host, ComponentCallbacks2 {
  ......
  @Override
  public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    //FlutterActivity的區別在于FlutterFragment在他自己的onAttach中實體化FlutterActivityAndFragmentDelegate并呼叫onAttach方法,
    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(context);
  }

  @Nullable
  @Override
  public View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    //FlutterActivity類似,只不過是在FlutterFragment對應生命周期回呼,
    return delegate.onCreateView(inflater, container, savedInstanceState);
  }
  ......
  //與FlutterActivity類似,只是這個方法不是Fragment自己框架回呼,需要依賴在Activity中呼叫,
  //譬如FlutterFragmentActivity中對應同名方法的實作,
  //注意這里的@ActivityCallThrough注解就是這個含義,
  @ActivityCallThrough
  public void onPostResume() {
    delegate.onPostResume();
  }
  ......
}

其實我們掌握了FlutterActivity之后,FlutterFragment核心基本上沒啥區別,主要都是通過代理FlutterActivityAndFragmentDelegate與Flutter平臺打交道,實作UI的展示,不做細說,下面繼續


FlutterFragmentActivity分析

從上面的FlutterFragment可以看到,FlutterFragmentActivity可以說是FlutterFragment的一個宿主Activity,給我們自己使用Activity系結FlutterFragment做了很好的參考,其內部與Flutter平臺的關聯關系都已經處理,用戶不需要再考慮上面的各種關聯方法的關聯,

FlutterFragmentActivity是一個基于 FragmentActivity 的 Flutter Activity,

FlutterFragmentActivity 的存在是因為生態系統中有一些 Android API 只接受一個 FragmentActivity, 如果不需要 FragmentActivity,則應考慮使用常規 FlutterActivity 代替,

我們繼續看FlutterFragmentActivity核心方法

onCreate方法

 @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    // 這里和FlutterActivity完全一樣,只是不用實體化一個FlutterActivityAndFragmentDelegate,因為其內部的FlutterFragment會做這些事
    switchLaunchThemeForNormalTheme();

    super.onCreate(savedInstanceState);

    configureWindowForTransparency();
    // 呼叫createFragmentContainer生成了一個View設定給Activity的content,用于展示FlutterFragment,接下來會創建FLutterFragment
    setContentView(createFragmentContainer());
    configureStatusBarForFullscreenFlutterExperience();
    //檢驗的是FlutterFragment是否添加OK,如果沒有就創建FlutterFragment并commit
    ensureFlutterFragmentCreated();
  }

需要關聯的方法呼叫

  @Override
  public void onPostResume() {
    super.onPostResume();
    flutterFragment.onPostResume();
  }

  @Override
  protected void onNewIntent(@NonNull Intent intent) {
    flutterFragment.onNewIntent(intent);
    super.onNewIntent(intent);
  }

  @Override
  public void onBackPressed() {
    flutterFragment.onBackPressed();
  }

注意上面FlutterFragment一端,提示的需要手動關聯的,在這里只是舉出了一部分;對于需要關聯或者我們自己使用Activity集成FlutterFragment的情形,這個FlutterFragmentActivity是個很好的參考實作,

總結

上面我們通過對FlutterActivity和FlutterFragment的了解,相信對Flutter UI如何被顯示到Android的Activity和Fragment有了一定的認識,Android原生控制元件只是作為一個容器,FlutterView才是真正展示FlutterUI的地方,

但是FlutterView的使用又不能跟普通原生View一樣使用,如果我們需要在自己的原生View里面直接嵌套FlutterView,就要在宿主類(Activity或者Fragment)中同樣類似FlutterActivity或者FlutterFragment一樣,通過delegate手動去實作各種引擎,FlutterUI的生命周期系結處理,

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

標籤:其他

上一篇:View的幾個小工具

下一篇:Retrofit VS OkHttp,誰是最強網路開發框架?

標籤雲
其他(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