📜 ⬆️ ⬇️

Android: dynamically load fragments from the network

In this article, we will look at how to download classes (including fragments) from the network at runtime, and use them in your Android application. The field of application of such technology in practice is a separate topic for conversation, but the implementation of this functionality seemed to me to be a rather interesting task.

Let's get started

Create a fragment


To begin with, let's create some fragment Fragment0 and implement the onCreateView () method in it:

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment //return inflater.inflate(R.layout.fragment1, container, false); LinearLayout linearLayout = new LinearLayout(getActivity()); linearLayout.setOrientation(LinearLayout.VERTICAL); linearLayout.setGravity(Gravity.CENTER); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); Button button = new Button(getActivity()); button.setText(""); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showFragment("jatx.networkingclassloader.dx.Fragment1", null); //    } }); linearLayout.addView(button, lp); return linearLayout; } 

The standard method of creating markup from xml in our case will not work, so for the first fragment we create it programmatically.
')
Next, we need to create an APK based on the module containing the fragment, unpack it using unzip, and put the classes.dex file on the server.

Implement class loading


In a separate module, create the NetworkingActivity class and implement the following methods in it:

 @Override protected void onCreate(Bundle savedInstanceState) { // ...... dataDir = getApplicationInfo().dataDir; frameLayout = (FrameLayout) findViewById(R.id.main_frame); progressDialog = new ProgressDialog(this); progressDialog.setIndeterminate(true); progressDialog.setMessage("   "); progressDialog.show(); //  classes.dex  ,    : DownloadTask downloadTask = new DownloadTask(this, dataDir); downloadTask.execute(null, null, null); // receiver   ,        : BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String className = intent.getStringExtra("className"); Bundle args = intent.getBundleExtra("args"); showFragment(className, args); } }; IntentFilter filter = new IntentFilter("jatx.networkingclassloader.ShowFragment"); registerReceiver(receiver, filter); } // ,   AsyncTask   c  classes.dex: public void downloadReady() { Toast.makeText(this, "   ", Toast.LENGTH_SHORT).show(); progressDialog.dismiss(); showFragment("jatx.networkingclassloader.dx.Fragment0", null); } public void showFragment(String className, Bundle arguments) { //   : File dexFile = new File(dataDir, "classes.dex"); Log.e("Networking activity", "Loading from dex: " + dexFile.getAbsolutePath()); //  ,   DexClassLoader: File codeCacheDir = new File(getCacheDir() + File.separator + "codeCache"); codeCacheDir.mkdirs(); //  ClassLoader: DexClassLoader dexClassLoader = new DexClassLoader( dexFile.getAbsolutePath(), codeCacheDir.getAbsolutePath(), null, getClassLoader()); try { //     : Class clazz = dexClassLoader.loadClass(className); //   : Fragment fragment = (Fragment) clazz.newInstance(); //      : fragment.setArguments(arguments); FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(R.id.main_frame, fragment); fragmentTransaction.commit(); } catch (Exception e) { e.printStackTrace(); } } 

We open other fragments from the fragment.


To do this, in the class LoadableFragment (the superclass of all our fragments) we implement the following method:

 public void showFragment(String className, Bundle args) { Intent intent = new Intent("jatx.networkingclassloader.ShowFragment"); intent.putExtra("className", className); intent.putExtra("args", args); getActivity().sendBroadcast(intent); } 

I hope everything is clear here.

We will try to create our next fragment a little differently.

We load xml markup from the network


To begin with, we create and upload a markup file to the server. I found a library on github that can parse xml layout from a string. To work correctly, I had to cut it a little .

And so, we add the following methods to our class LoadableFragment :

 protected void loadLayoutFromURL(FrameLayout container, String url) { this.container = container; //   : LayoutDownloadTask layoutDownloadTask = new LayoutDownloadTask(this, url); layoutDownloadTask.execute(null, null, null); } // ,  xml-  : public void onLayoutDownloadSuccess(String xmlAsString) {} 

Now, using this all create a fragment Fragment1 :

 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { FrameLayout frameLayout = new FrameLayout(getActivity()); loadLayoutFromURL(frameLayout, "http://tabatsky.ru/testing/fragment1.xml"); return frameLayout; } @Override public void onLayoutDownloadSuccess(String xmlAsString) { LinearLayout linearLayout = (LinearLayout) DynamicLayoutInflator.inflate(getActivity(), xmlAsString, container); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); linearLayout.setLayoutParams(lp); final EditText editText = (EditText) DynamicLayoutInflator.findViewByIdString(linearLayout, "edit_text"); Button button = (Button) DynamicLayoutInflator.findViewByIdString(linearLayout, "button"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bundle args = new Bundle(); args.putString("userName", editText.getText().toString()); showFragment("jatx.networkingclassloader.dx.Fragment2", args); } }); } 

Afterword


The full source code of the project can be viewed on github . Ready APK can be downloaded here .

And finally, I want to say a few words about the possible use of this technology: for example, you can issue different classes.dex from the server depending on the type of user account (paid / free), which should somewhat increase the complexity of the reverse engineering of the application.

Source: https://habr.com/ru/post/331122/


All Articles