iOS程序員必須知道的Android要點

jopen 10年前發布 | 56K 次閱讀 Android Android開發 移動開發

在移動應用飛速發展的今天,APP只針對IOS平臺進行開發已經不夠了,如今Android在移動設備占有近80%的市場,如此大量的潛在用戶怎么能被忽略掉呢。

在這篇文章中,本人會介紹在IOS開發中,怎么學習一些Android開發的理念,Android和IOS功能上本身有一定的相似之處,但是具體實現的方式各異,所以這篇文章會使用一個項目例子進行對比,說明怎么在這兩個平臺上分別去實現這個任務。

除了了解IOS的開發知識,本文還需要對Java有一定的了解,并能夠安裝和使用ADT(Android Development Tools)。此外,如果你是一個Android新手,那么請試試去看看Android的官方教程—— building your first app,非常有用。

UI設計簡要說明

本文不會深入研究關于IOS和Android兩個平臺之間的用戶體驗或者設計模式之間的差異,不過如果能夠理解Android上的一些優秀的UI范例也很有幫助:ActionBar、Overflow menu、back button share action等等。假如你很想嘗試Android開發,那么強烈推薦你去Google Play Store上購置一臺Nexus5,然后把它作為你日常使用的設備使用一周,然后嘗試仔細了解這個操作系統的各種功能和擴展特性,如果開發者連操作系統的各種使用規則都不了解,那么做出來的產品一定有問題。

編程語言的應用框架

Objective-C和Java之間有很多不同之處,如果把Objective-C的編程風格帶到Java里面的話,很多代碼也許會和底層的應用框架沖突。簡單地說,就是需要注意一些區別:

  • 去掉Objective-C里面的類前綴,因為Java里有顯式的命名空間和包結構,所以就沒必要用類前綴了。
  • 實例變量的前綴用“m”,不用“_”,在寫代碼的過程中要多利用JavaDoc文檔。這樣能使代碼更清晰,更適合團隊合作。
  • 注意檢查NULL值,Objective-C對空值檢查做的很好,不過Java沒有。
  • 不直接使用屬性,如果需要settergetter,需要創建一個getVariableName()方法,然后顯式調用它。如果直接使用“this.object”不會調用自定義的getter方法,你必須使用this.getObject這樣的方法。
  • 同樣的,方法命名時帶有getset前綴來標示它是gettersetter方法,Java的方法很喜歡寫成actions或者queries等,比如Java會使用getCell(),而不用cellForRowAtIndexPath
  • </ul>

    項目結構

    Android應用程序主要分為兩部分。第一部分是Java源代碼,以Java包結構排布,也可以根據自己的喜好進行結構排布。最基本的結構就是分為這幾個頂層目錄:activities、fragments、views、adapters和data(models和managers)。

    第二部分是res文件夾,就是“resource”的簡稱,res目錄存放的是圖片、xml布局文件,還有其它xml值文件,是非代碼資源的一部分。在IOS上,圖片只需要匹配兩個尺寸,而在Android上有很多種屏幕尺寸需要考慮,Android上用文件夾來管理管理圖片、字符串,還有其它的屏幕配置數值等。res文件夾里也含有類似IOS中xib文件的xml文件,還有存儲字符串資源、整數值,以及樣式的xml文件。

    最后,在項目結構上還有一點相似的地方,就是AndroidManifest.xml文件。這個文件相當于IOS的Project-Info.plist文件,它存儲了activities、application還有Intent的信息,要了解更多關于Intent的資料,可以繼續閱讀這篇文章

    Activities

    Activities是Android APP最基本的可視單元,就像UIViewControllers是IOS最基本的顯示組件一樣。Android系統使用一個Activity棧來管理Activity,而IOS使用UINavigationController進行管理。當APP啟動的時候,Android系統會把Main Activity壓棧,值得注意的是這是還可以再運行別的APP Activity,然后把它放到Activity棧中。返回鍵默認會從Activity棧進行pop操作,所以如果用戶按下返回鍵,就可以切換運行已運行的App了。

    Activities還可以用Intent組件初始化別的Activity,初始化時可攜帶數據。啟動一個新的Activity類似于IOS上創建一個UIViewController。最基本的啟動一個新的Activity的方式就是創建一個帶有data的Intent組件。Android上實現自定義Intent初始化器的最好方法就是寫一個靜態getter方法。在Activity結束的時候也可以返回數據,在Activity結束的時候可以往Intent里面放置額外的數據。

    IOS和Android的一個大的區別是,任何一個在AndroidManifest文件中注冊的Activity都可以作為程序的入口,為Activity設置一個intent filter屬性比如“media intent”,就可以處理系統的媒體文件了。最好的例子就是編輯照片Activity。它可以打開一張照片,然后進行修改,最后在Activity結束時返回修改后的照片。

    附加提醒:要想在Activity和Fragment之間傳遞對象,必須要實現Parcelable接口,就像在IOS里需要遵循協議一樣。還有,Parcelable對象可以存在于Activity或者Fragment的savedInstanceState里,這樣在它們被銷毀后可以更容易重建它們的狀態。

    下面就來看看怎么在一個Activity中啟動另一個Activity,然后在第二個Activity結束時進行返回。

    啟動其它Activity并返回結果

        // A request code is a unique value for returning activities
        private static final int REQUEST_CODE_NEXT_ACTIVITY = 1234;

    protected void startNextActivity() {
        // Intents need a context, so give this current activity as the context
        Intent nextActivityIntent = new Intent(this, NextActivity.class);
           startActivityForResult(nextActivityResult, REQUEST_CODE_NEXT_ACTIVITY);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case REQUEST_CODE_NEXT_ACTIVITY:
            if (resultCode == RESULT_OK) {
                // This means our Activity returned successfully. For now, Toast this text.  
                // This just creates a simple pop-up message on the screen.
                    Toast.makeText(this, "Result OK!", Toast.LENGTH_SHORT).show();
                }
                return;
            }    
            super.onActivityResult(requestCode, resultCode, data);
    }</pre> <h3>Activity結束時返回數據</h3>
    

        public static final String activityResultString = "activityResultString";

    /*
     * On completion, place the object ID in the intent and finish with OK.
     * @param returnObject that was processed
     */
    private void onActivityResult(Object returnObject) {
            Intent data = new Intent();
            if (returnObject != null) {
                data.putExtra(activityResultString, returnObject.uniqueId);
            }
    
            setResult(RESULT_OK, data);
            finish();        
    }</pre> <h3>Fragments</h3>
    

    Fragment的概念在Android上比較獨特,從Android3.0開始引入。Fragment是一個迷你版的控制器,可以顯示在Activity上。它有自己的狀態和邏輯,同時在一個屏幕上支持多個Fragment同時顯示。Activity充當Fragment的控制器,Fragment沒有自己的上下文環境,只能依賴Activity存在。

    使用Fragment最好的例子就是在平板上的應用。可以在屏幕左邊放一個fragment列表,然后在屏幕的右邊放fragment的詳細信息。Fragment可以把屏幕分成可重復利用的小塊,分別控制管理。不過要注意Fragment的生命周期,會有些細微的差別。

     iOS程序員必須知道的Android要點

    Fragment是實現Android結構化的一種新的方式,就像IOS中的不用UITableview而用UICollectionView實現列表數據結構化。因為只使用Activity而不用Fragment的話,會簡單一些。不過,之后你會遇到麻煩。如果不使用Fragment代替全盤使用Activity的話,在后面需要利用intent和進行多屏幕支持的時候就會遇到困難。

    下面看一個UITableViewController的例子和一個ListFragment的地鐵時刻表示例。

    表格實現

     iOS程序員必須知道的Android要點

        @interface MBTASubwayTripTableTableViewController ()

    @property (assign, nonatomic) MBTATrip *trip;
    
    @end
    
    @implementation MBTASubwayTripTableTableViewController
    
    -(instancetype)initWithTrip:(MBTATrip *)trip
    {
        self = [super initWithStyle:UITableViewStylePlain];
        if (self) {
            _trip = trip;
            [self setTitle:trip.destination];
        }
        return self;
    }
    
    -(void)viewDidLoad
    {
        [super viewDidLoad];
    
        [self.tableView registerClass:[MBTAPredictionCell class] forCellReuseIdentifier:[MBTAPredictionCell reuseId]];
        [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([MBTATripHeaderView class]) bundle:nil] forHeaderFooterViewReuseIdentifier:[MBTATripHeaderView reuseId]];
    }
    
    #pragma mark - UITableViewDataSource
    
    -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 1;
    }
    
    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return [self.trip.predictions count];
    }
    
    #pragma mark - UITableViewDelegate
    
    -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    {
        return [MBTATripHeaderView heightWithTrip:self.trip];
    }
    
    -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
    {
        MBTATripHeaderView *headerView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:[MBTATripHeaderView reuseId]];
        [headerView setFromTrip:self.trip];
        return headerView;
    }
    
    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MBTAPredictionCell reuseId] forIndexPath:indexPath];
    
        MBTAPrediction *prediction = [self.trip.predictions objectAtIndex:indexPath.row];
        [(MBTAPredictionCell *)cell setFromPrediction:prediction];
    
        return cell;
    }
    
    -(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return NO;
    }
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
    
    @end</pre> <h3>List Fragment實現</h3>
    

     iOS程序員必須知道的Android要點

        public class TripDetailFragment extends ListFragment {

        /**
         * The configuration flags for the Trip Detail Fragment.
         */
        public static final class TripDetailFragmentState {
            public static final String KEY_FRAGMENT_TRIP_DETAIL = "KEY_FRAGMENT_TRIP_DETAIL";
        }
    
        protected Trip mTrip;
    
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param trip the trip to show details
         * @return A new instance of fragment TripDetailFragment.
         */
        public static TripDetailFragment newInstance(Trip trip) {
            TripDetailFragment fragment = new TripDetailFragment();
            Bundle args = new Bundle();
            args.putParcelable(TripDetailFragmentState.KEY_FRAGMENT_TRIP_DETAIL, trip);
            fragment.setArguments(args);
            return fragment;
        }
    
        public TripDetailFragment() { }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            Prediction[] predictions= mTrip.predictions.toArray(new Prediction[mTrip.predictions.size()]);
            PredictionArrayAdapter predictionArrayAdapter = new PredictionArrayAdapter(getActivity(), predictions);
            setListAdapter(predictionArrayAdapter);
            return super.onCreateView(inflater,container, savedInstanceState);
        }
    
        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            TripDetailsView headerView = new TripDetailsView(getActivity());
            headerView.updateFromTripObject(mTrip);
            getListView().addHeaderView(headerView);
        }
    }</pre> <p>下面,我們來分析Android上特有的一些組件。</p>
    

    Android通用組件

    ListView和Adapter

    ListView和IOS的UITableView最像,也是使用最頻繁的組件之一。類似于UITableView的UITableViewControllerListView也有一個ListActivity,還有ListFragment。這些組件會更好地處理一些布局問題,也為操作數據適配器提供了便利,這個接下來會說到。下面這個例子就是使用ListFragment來展示數據,類似TableViewdatasource

    關于datasource,Android上沒有datasource和delegate,只有Adapter。Adapter有很多種形式,主要功能其實就是為了把datasource和delegate合在一起。Adapter拿到數據然后填充到Listview中,在ListView中初始化響應的組件并顯示出來,下面是arrayAdapter的使用:

        public class PredictionArrayAdapter extends ArrayAdapter<Prediction> {

        int LAYOUT_RESOURCE_ID = R.layout.view_three_item_list_view;
    
        public PredictionArrayAdapter(Context context) {
            super(context, R.layout.view_three_item_list_view);
        }
    
        public PredictionArrayAdapter(Context context, Prediction[] objects) {
            super(context, R.layout.view_three_item_list_view, objects);
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {
            Prediction prediction = this.getItem(position);
            View inflatedView = convertView;
            if(convertView==null)
            {
                LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                inflatedView = inflater.inflate(LAYOUT_RESOURCE_ID, parent, false);
            }
    
            TextView stopNameTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_left_text_view);
            TextView middleTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_middle_text_view);
            TextView stopSecondsTextView = (TextView)inflatedView.findViewById(R.id.view_three_item_list_view_right_text_view);
    
            stopNameTextView.setText(prediction.stopName);
            middleTextView.setText("");
            stopSecondsTextView.setText(prediction.stopSeconds.toString());
    
            return inflatedView;
        }
    }</pre> <p>可以看到,adapter里面有一個很重要的方法叫getView,和IOS的<code>cellForRowAtIndexPath</code>方法一樣。還有一個相似之處就是循環利用的策略,和IOS6上的實現很相似。在Android和IOS上循環利用View都很重要,事實上它對列表的實現有很大幫助。這個adapter很簡單,使用了一個內建的類<code>ArrayAdapter</code>來存放數據,也解釋了怎么把數據填入<code>ListView</code>中。</p>
    

    AsyncTask

    對于IOS上的Grand Central Dispatch,Android上也有AsyncTask。它是異步操作工具的又一選擇,用一種很友好的方式實現異步任務。不過AsyncTask有點超出了本文的范圍,所以本人還是推薦你看看這里

    Activity的生命周期

    IOS開發者在寫Android的過程中還要注意的就是Android的生命周期。可以先從Activity的生命周期文檔開始:

     iOS程序員必須知道的Android要點

    本質上Activity的生命周期很像UIViewController的生命周期,主要區別在于Android上可以任意銷毀Activity,所以保證Activity的數據和狀態很重要,如果在onCreate()中保存了的話,可以在saved state中恢復Activity的狀態。最好的方法就是使用saveInstanceState來存儲bundled數據,例如下面的TripListActivity是示例工程的一部分,用來保存當前顯示的數據:

        public static Intent getTripListActivityIntent(Context context, TripList.LineType lineType) {
            Intent intent = new Intent(context, TripListActivity.class);
            intent.putExtra(TripListActivityState.KEY_ACTIVITY_TRIP_LIST_LINE_TYPE, lineType.getLineName());
            return intent;
        }

    public static final class TripListActivityState {
        public static final String KEY_ACTIVITY_TRIP_LIST_LINE_TYPE = "KEY_ACTIVITY_TRIP_LIST_LINE_TYPE";
    }
    
    TripList.LineType mLineType;    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       mLineType = TripList.LineType.getLineType(getIntent().getStringExtra(TripListActivityState.KEY_ACTIVITY_TRIP_LIST_LINE_TYPE));
    }    </pre> <p>還有一個要注意的地方就是屏幕旋轉:如果屏幕發生旋轉,會改變Activity的生命周期。也就是說,Activity會先被銷毀,然后再重建。如果已經保存了數據和狀態,Activity可以重建原來的狀態,實現無縫重建。很多APP開發者在遇到APP旋轉時會出現問題,因為Activity沒有處理旋轉的改變。<strong>注意</strong>不要用鎖定屏幕的方向來解決這個問題,因為這樣會存在一個隱含的生命周期的bug,在某些情況下還是可能發生的。</p>
    

    Fragment生命周期

    Fragment的生命周期和Activity的很像,但是有一些區別:

     iOS程序員必須知道的Android要點

    還有一個問題就是Fragment和Activity通信的問題。需要注意的是onAttach()方法在onActivityCreated()方法之前被調用,這就意味著在fragment創建完成后Activity還不能保證已經存在。如果需要為父Activity設置接口或者代理,則需要在onActivityCreated()方法調用之后。

    Fragment也有可能會在系統需要的時候被創建和銷毀。如果要保存它的狀態,那么也要像Activity一樣進行處理。下面這個是示例項目中的一個小例子,trip列表Fragment會記錄相應的數據,和上面的地鐵時間示例一樣:

        /**

     * The configuration flags for the Trip List Fragment.
     */
    public static final class TripListFragmentState {
        public static final String KEY_FRAGMENT_TRIP_LIST_LINE_TYPE = "KEY_FRAGMENT_TRIP_LIST_LINE_TYPE";
        public static final String KEY_FRAGMENT_TRIP_LIST_DATA = "KEY_FRAGMENT_TRIP_LIST_DATA";
    }
    
    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param lineType the subway line to show trips for.
     * @return A new instance of fragment TripListFragment.
     */
    public static TripListFragment newInstance(TripList.LineType lineType) {
        TripListFragment fragment = new TripListFragment();
        Bundle args = new Bundle();
        args.putString(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_LINE_TYPE, lineType.getLineName());
        fragment.setArguments(args);
        return fragment;
    }
    
    protected TripList mTripList;
    protected void setTripList(TripList tripList) {
        Bundle arguments = this.getArguments();
        arguments.putParcelable(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_DATA, tripList);
        mTripList = tripList;
        if (mTripArrayAdapter != null) {
            mTripArrayAdapter.clear();
            mTripArrayAdapter.addAll(mTripList.trips);
        }
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mLineType = TripList.LineType.getLineType(getArguments().getString(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_LINE_TYPE));
            mTripList = getArguments().getParcelable(TripListFragmentState.KEY_FRAGMENT_TRIP_LIST_DATA);
        }
    }    </pre> <p>還要注意的是,Fragment經常會在<code>onCreate</code>方法中利用<code>bundled</code>參數重建自己的狀態。而自定義的Trip列表模型類相關的<code>setter</code>方法也會把對象添加到<code>bundled</code>參數中。這樣就可以保證在Fragment被銷毀或者重建時,比如屏幕旋轉后,可以利用最新的數據去重建狀態。</p>
    

    關于布局

    和Android上其它部分的開發工作一樣,指定布局文件也有自己的優缺點。Android上的布局文件都存放在res/layouts文件夾中,以易讀的xml形式存儲。

    地鐵列表布局
     iOS程序員必須知道的Android要點

        <RelativeLayout xmlns:android="
    
    
        <ListView
            android:id="@+id/fragment_subway_list_listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingBottom="@dimen/Button.Default.Height"/>
    
        <Button
            android:id="@+id/fragment_subway_list_Button"
            android:layout_width="match_parent"
            android:layout_height="@dimen/Button.Default.Height"
            android:minHeight="@dimen/Button.Default.Height"
            android:background="@drawable/button_red_selector"
            android:text="@string/hello_world"
            android:textColor="@color/Button.Text"
            android:layout_alignParentBottom="true"
            android:gravity="center"/>
    
    </RelativeLayout></pre> <p>下面這個是IOS上用UITableView和UIButton來制作的類似效果:</p>
    

     iOS程序員必須知道的Android要點

     iOS程序員必須知道的Android要點

    可以發現,Android的布局文件更容易閱讀和理解,而且提供了多種布局方式,我們只介紹了其中的一小部分。

    通常來說,我們接觸的最基本的UI結構就是ViewGroup的子類,RelativeLayout、LinearLayout、FrameLayout是最常用的。這些ViewGroup的子類可以容納別的View,并包含了一些排布控件的屬性。

    一個很好的例子就是上面用到的RelativeLayout,在里面可以使用android:layout_alignParentBottom="true"來把按鈕定位到布局底部。

    最后,如果要在Fragment或者Activity中使用這些控件的話,可以在onCreateView()方法中使用布局的資源ID:

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_subway_listview, container, false);
        }

    布局小貼士

    • 請使用dp(density-independent pixels),不直接使用dx(pixels);
    • 不要在可視化編輯器中移動布局組件——通常來說可視化編輯器在你調好高和寬后,會為組件添加一些多余的像素,所以最好就是直接操作xml文件;
    • 如果在布局的heightwidth看到有用fill_parent這個屬性的話,你會發現在API 8的時候這個屬性就已經被限制了,改用match_parent替換。
    • </ul>

      如果要了解更多關于這方面的內容可以看看這篇文章——responsive android applications

      數據

      Android上的數據存儲也和IOS上差不多:

      • SharedPreferences、NSUserDefaults;
      • 內存存儲對象;
      • internal、external文件讀寫document directory文件讀寫;
      • SQLite數據庫存儲Core Data形式數據庫存儲。
      • </ul>

        其中最基本的區別就是Core Data。在Android上可以直接訪問sqlite數據庫并可以返回cursor對象得到結果。更詳細的介紹請看這篇文章—— using SQLite on Android

        Android課后作業

        之前已經討論的東西只是描述了Android的大概 ,要想好好利用Android上的更多的特性,本人建議你看看下面的這些概念:

        • ActionBar,Overflow Menu,還有Menu Button;
        • 跨應用間數據共享;
        • 響應系統actions;
        • 好好學習Java的特性:泛型、抽象方法和抽象類等等;
        • 看看Google的低版本兼容庫;
        • 關于Android上的模擬器:可以安裝x86 HAXM plugin來使模擬器更流暢。
        • </ul>

          最后的工作

          以上所有涉及的知識點都在MBTA中實現了(托管在Github上)。這個項目僅僅是為了解釋兩個不同平臺之間的一些基本的概念,比如應用架構、數據處理、界面開發等。

          我們可以學到更多的解決問題的技巧和方式。因為兩平臺的實現細節各不相同,也許了解Android的工作原理可以對IOS的下一個版本的開發工作有所幫助。系統之間有很多相似的地方,誰知道下個版本的IOS會出現什么呢?


          1.源代碼 Source

          2.關于多屏幕支持的google官方文檔:http://developer.android.com/guide/practices/screens_support.html

          3.關于 Intents 的文檔:here

          4.關于平板分塊處理的文檔資料: multi-pane tablet view

          5.感謝 NSHipster


          原文鏈接: objc   翻譯: 伯樂在線 - chris
          譯文鏈接: http://blog.jobbole.com/67098/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!