Android IOC思想入門と実戦

36665 ワード

  • IOCとは
  • 制御反転(Inversion of Control、IoCと略す)は、コンピュータコード間の結合度を低減するために使用できるオブジェクト向けプログラミングにおける設計原則である.その中で最も一般的な方法は依存注入(Dependency Injection、略称DI)と呼ばれ、もう一つの方法は「依存ルックアップ」(Dependency Lookup)と呼ばれている.制御反転により、オブジェクトは作成されると、制御システム内のすべてのオブジェクトの外部エンティティによって依存するオブジェクトの参照が伝達されます.依存はオブジェクトに注入されるともいえる.
    簡単に言えば、IOCはプログラムコードからアクティブに取得されたリソースであり、サードパーティが取得して元のコードを受動的に受信する方式を転換し、デカップリングの効果を達成することを制御反転と呼ぶ.
    例:ButterKnifeのシミュレーションを例に挙げます.レイアウトファイルとviewのfindViewイベントとonClickイベントを注入します.
  • まず使い方を見てみる:
  • //             BaseActivity 
    
    public abstract class BaseActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            InjectUtils.inject(this);
        }
    }
    
    //  MainActivity        
    @ContentView(R.layout.activity_main)
    public class MainActivity extends BaseActivity {
    	//     `        `
        @InjectView(R.id.btn1)
        private Button btn1;
        @InjectView(R.id.btn2)
        private Button btn2;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.e(TAG, btn1.toString());
            Log.e(TAG, btn2.toString());
        }
    	//     
        @OnClick({R.id.btn1, R.id.btn2})
        public void click(View view) {
            Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
        }
    }
    
    
  • 定義注記
  • //                 layoutId          class
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ContentView {
        int value();
    }
    
    //     view    viewId            
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InjectView {
        int value();
    }
    
    //                               
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface BaseEvent {
        //        setOnClickListener / setonLongClick       
        String setListenerType();
        // new Onclick / onLongClick           
        Class<?> newListenerType();
    }
    
    
    //             
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    //       setOnClickListener           setOnLongClickListener
    @BaseEvent(setListenerType = "setOnClickListener"
            , newListenerType = View.OnClickListener.class)
    public @interface OnClick {
    	//    viewid           ButterKnife   @Click({R.id.btn,R.id.text})
        int[] value();
    }
    
  • 具体的な初期化注入コードは以下の通り:
  • 
    /**
     * ioc      
     */
    class InjectUtils {
    
        static void inject(Object context) {
            injectLayout(context);
            injectView(context);
            injectEvent(context);
        }
    
        /**
         *    layout
         *
         * @param context context
         */
        private static void injectLayout(Object context) {
            //    calss
            Class clazz = context.getClass();
            //     
            ContentView contentView = (ContentView) clazz.getAnnotation(ContentView.class);
            if (contentView != null) {
                try {
                    //       id
                    int layoutId = contentView.value();
                    //        activity   setContentView   ,  id
                    Method method = clazz.getMethod("setContentView", int.class);
                    method.invoke(context, layoutId);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         *   view
         *
         * @param context context
         */
        private static void injectView(Object context) {
            //    activity class
            Class clazz = context.getClass();
            //        
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //           
                InjectView annotation = field.getAnnotation(InjectView.class);
                if (annotation != null) {
                    try {
                        //        private      
                        field.setAccessible(true);
                        //   viewId
                        int viewId = annotation.value();
                        //        activity   findViewById
                        Method method = clazz.getMethod("findViewById", int.class);
                        View view = (View) method.invoke(context, viewId);
                        //   findViewById          view      
                        field.set(context, view);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         *       
         */
        private static void injectEvent(Object context) {
            Class clazz = context.getClass();
            //         
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method method : declaredMethods) {
                //                      
                Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
                for (Annotation annotation : declaredAnnotations) {
                    Class<?> aClass = annotation.annotationType();
                    //         baseEvent                  
                    BaseEvent baseEvent = aClass.getAnnotation(BaseEvent.class);
                    if (baseEvent == null) {
                        continue;
                    }
                    //            onClick    onLongClick
                    String listenerType = baseEvent.setListenerType();
                    //           new View.OnclickListener
                    Class<?> newListenerType = baseEvent.newListenerType();
                    try {
                        //       view
                        Method valueMethod = aClass.getDeclaredMethod("value");
                        int[] viewIds = (int[]) valueMethod.invoke(annotation);
                        for (int viewId : viewIds) {
                            Method findViewById = clazz.getMethod("findViewById", int.class);
                            View view = (View) findViewById.invoke(context, viewId);
                            if (view == null) {
                                continue;
                            }
                            //           setOnClickListener    setOnLongClickListener
                            //    InvocationHandler
                            ListenerInvocationHandler invocationHandler = new ListenerInvocationHandler(context, method);
                            //      
                            Object proxy = Proxy.newProxyInstance(newListenerType.getClassLoader(), new Class[]{newListenerType}, invocationHandler);
                            Method eventMethod = view.getClass().getMethod(listenerType, newListenerType);
                            eventMethod.invoke(view, proxy);
                        }
                    } catch (Exception e) {
                        Log.e("exception", e.toString());
                    }
                }
            }
        }
    }
    
  • InvocationHandler類:
  • 
    public class ListenerInvocationHandler implements InvocationHandler {
    	//                   activity dialog 
        private Object object;
        //       
        private Method method;
    
        public ListenerInvocationHandler(Object object, Method method) {
            this.object = object;
            this.method = method;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return this.method.invoke(object, args);
        }
    }
    
    
  • 新しいonLongClickListenerを書くには、次のように注釈を追加します.
  • @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    //      baseEvent        
    @BaseEvent(setListenerType = "setOnLongClickListener"
            , newListenerType = View.OnLongClickListener.class)
    public @interface OnLongClick {
        int[] value() default -1;
    }
    
  • MainActivityで呼び出す
  • 	//	         onLongClick                      true
    	@OnLongClick({R.id.btn1, R.id.btn2})
        public boolean onLongClick(View view){
            Toast.makeText(this, "longClick", Toast.LENGTH_SHORT).show();
            return true;
        }
    

    IOCの基本思想はもう終わりました.IOCは私たちがよく知っているButterKnife、javaのSpringMVCなど、多くのフレームワークに応用されたいと考えています.これにより結合度を低減することができる.今後のメンテナンスコストを削減します.しかし、オブジェクトを作成するプロセスはやや複雑で、通常の書き方よりも効率がやや低い.