建立一个属于自己的框架

1、拥有一套属于自己的框架的重要性


拥有属于自己的一套框架和代码库是非常重要的事情。这里面有个词语“属于”,嗯,没错,只有属于自己的事物才能真正掌控着并把它玩转起来。

一套框架、一套成熟的解决方案、一套成熟的代码库是你去谈项目或者和产品经理交涉时候的筹码,它有多少,你心中就有多少底,即使你的学习能力再强,也是不能胜过已经有的代码,因为商业项目讲求的是质量 + 效率,只有经过多次实践而不败的代码才是拥有高质量的,而不是把 demo 实现了就拿来用这么简单。

由于我没有一套属于自己的框架(或称成熟的架构)。所以在做这外包之前就开始写一属于自己的框架。既然是自己的东西,就无需强求高度集成丰富的组建,而是根据实际的业务需求而定制一套框架,所以,框架并不是能万能套用,而是要看需求、看情况而定,盲目使用“漂亮”的框架的后果之一就是造成项目的臃肿。

注:本文说的框架,指的是一应用的体系结构,如:应用骨架 + 网络访问 + 数据库存取 + 消息通知 +UI 显示。

本次构建的框架主要用到以下的组建:
  • 1、Activity + Fragment

    单个 Activity 控制多个 fragment,每个 fragment 充当控制器的角色,每个页面对应自己的控制器,控制器控制自己的 view(界面),view 用 java 实现,view 中也绑定自己对应的控制器,页面的跳转使用用 eventbus 通知 activity 进行跳转。

  • 2、ORM 使用 Activeandroid

    操作数据库还是使用 ORM 比较好,相对传统的 SqlHelper 更方便简单,不易出错,减少代码工作量。

  • 3、网络请求使用 retrofit,中间自己做了个解析器

    网络请求返回的数据自动用 gson 解析并赋入对应的数据结构 bean 类,和 ORM 的性质有点相似,因为 retrofit 只是网络请求并解析,并没有封装好回调到 UI 线程的消息通知机制,所以自己实现了一个。

如果这一切都只是放在 demo 的角度来实现的话,都是很简单的事情。但如果放在一个商业项目里面去实践,便会发现好多问题,这里指的不是单单我上面提到的,而是每一种技术或者解决方案,真的要拿到真实场景去实践才能测试到问题的存在。

在这里面主要探讨一下页面跳转的结构体,我已经快给多个 fragment、fragment 嵌套的问题搞死了。也终于明白到为什么 square 建议不要用 fragment。不过致命的 bug 都解决得七七八八。不过还是存在一些奇怪现象,这得再另外一篇博文去探讨了。用 fragment 始终比用多个 activity 跳转这个老方法好多了。

2、使用 Fragment 实现页面跳转方法


单一 Activity,布局仅需要一个 FrameLayout。

页面切换的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 最底层的页面
    private void setFragment(Fragment mTargetFragment){
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction
                .replace(mMainLayout.getId(), mTargetFragment, 				mTargetFragment.getClass().getName())
                .setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                .commit();
    }

    // 弹出页面
    public void popFragmant(Fragment from, Fragment to) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.hide(from)
        .add(mMainLayout.getId(), to, to.getClass().getName())
        .addToBackStack(to.getClass().getName())
        .commit();
    }
    
    // 弹出页面, dialog 形式
    public void popFragmant(Fragment to) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        if (!to.isAdded()) {
            transaction
                    .add(mMainLayout.getId(), to, to.getClass().getName())
                    .addToBackStack(to.getClass().getName())
                    .commit();
        } 
    }
    
    // 关闭页面
    public void closeFragment(Fragment mTargetFragment){
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction
                .remove(mTargetFragment)
                .commit();
        getSupportFragmentManager().popBackStack();
    }

    // 关闭所有页面
    public void closeAllFragment(){
        int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
        for (int i = 0; i < backStackCount; i++) {
            int backStackId = getSupportFragmentManager().getBackStackEntryAt(i).getId();
            getSupportFragmentManager().popBackStack(backStackId, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
    }
    
    // 最底层的页面
    public void onEvent(Event.SetFragmentEvent event){
        setFragment(event.mFragment);
    }
    
    // 弹出页面
    public void onEvent(Event.OpenFragmentEvent event){
        popFragmant(event.fromFragment, event.toFragment);
    }

    // 关闭页面
    public void onEvent(Event.CloseFragmentEvent event){
        closeFragment(event.mFragment);
    }

    // 关闭所有页面
    public void onEvent(Event.CloswAllFragmentEvent event){
        closeAllFragment();
    }
    
    // 弹出页面, dialog 形式
    public void onEvent(Event.PopFragment event){
    	popFragmant(event.toFragment);
    }

在 Activity 的 onCreate 方法里面初始化并显示第一个页面:

1
2
3
4
5
6
mMainLayout = new FrameLayout(this);
mMainLayout.setId(1);
setContentView(mMainLayout);

mWelcomeFragment = new WelcomeFragment();
setFragment(mWelcomeFragment);

设置最底层页面,显示第一个 welcome 页面,以及 welcome 页面之后跳转到的的主页,这种情况就要把底层页面替换掉,则要用上面的setFragment(Fragment mTargetFragment),这里面FragmentTransaction调用了它里面的replace方法,所以被替换的页面会被销毁,新换上的页面则是最底层的页面。

在底层页面 A 上面弹出一个页面 B,则需要popFragmant(Fragment from, Fragment to)方法。

1
2
3
transaction.hide(from)
        .add(mMainLayout.getId(), to, to.getClass().getName())
        .addToBackStack(to.getClass().getName())

这里只是把页面 A 隐藏掉(hide),并且加入到回退栈里。

但如果想弹出一个类似于弹窗这样的页面,即有透明部分的一个页面,则不能调用hide方法,否则页面 A 会被隐藏掉,这样的话透明就不存在意义了。

这样做的好处有:
1、页面可实例化
2、可设置回调
3、更易于测试

3、页面,逻辑分离


我在 Fragment 里面我做了哪些事情?

Fragment 在这里充当的角色是一个控制器。

由于我是用 JAVA 布局界面,所以我会在 Fragment 里面实例化我的界面类,并在onCreateView return 这个界面,如:

1
2
3
4
5
6
7
8
9
10
private WelcomeView mWelcomeView;

@Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mWelcomeView = new WelcomeView(this);
}

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return mWelcomeView;
}

这样我就可以在WelcomeView里面做布局界面的工作,并同时实现界面的逻辑,这样就不像在 xml 里面纯实现布局,而界面逻辑还需要在 Activity 去 findviewbyid,再去写界面逻辑。

注,上面说的是界面逻辑,而不是数据获取、处理、展示、以及跳转的逻辑。这些逻辑是放在 fragment 里面去做的。如 Fragment 里面实现一个从网络获取数据的方法,获取、处理后,把要展示的数据通过 View 的实例(在这里我们不是有 View 的实例吗)设置到相关的方法,这样就很好做到逻辑和界面分离。

4、遇到的问题


由于多 fragment 嵌套导致的一个问题,onActivityResult不能成功被调用,这里我也没找到真正的原因,只是找了个解决方法。

1
2
3
4
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
    mCurrentFragment.onActivityResult(requestCode, resultCode, data);
    super.onActivityResult(requestCode, resultCode, data);
}

在父 fragment 实现onActivityResult方法,然后在里面调用子 fragment 的onActivityResult即可。